{"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":"#!ek9\ndefines 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_add_program","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. Make the file executable with chmod u+x hello.ek9 and then run it with ./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":"#!ek9\ndefines 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","How do I print a message to the console in EK9?","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":"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 diagnostic messages, 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":"#!ek9\ndefines 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","message","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() checks if more input is available\n  - next() gets the next line as String\n  - ? is the isSet operator, true when input is 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":"#!ek9\ndefines 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?","What is the structure of an EK9 source file?","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 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":"#!ek9\ndefines 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","indent","intro","layout","migrate","module","ordering","organize","program","section","skeleton","source","start","structure"],"primaryTopics":["source file structure","file structure","program structure","defines module","defines program"],"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- By convention module names are lowercase with dot separators (the compiler does not enforce the case)\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":["package tags","tags field","tag package"],"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`:\npublicAccess <- true means anyone can download and use the package.\npublicAccess <- false means 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":"#!ek9\ndefines 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":["package license","license agreement","publicAccess license"],"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":"#!ek9\ndefines 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":["devDeps","development dependencies","deps vs devDeps"],"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":"#!ek9\ndefines 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":["excludeDeps","exclude transitive","transitive dependencies"],"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):\nRun these three commands in order: git clone https://github.com/stephenjohnlimb/ek9.git, then cd ek9, then mvn clean install.\nThe JAR appears at compiler-cli/target/ek9c-jar-with-dependencies.jar.\nThe native wrapper is built via CMake during the Maven build.\n\nOn Linux/macOS, EK9 files with the #!ek9 shebang can run directly:\nMake the file executable with chmod u+x hello.ek9, then run it with ./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":"#!ek9\ndefines 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 - but ONLY once those files are in the same BUILD, which is what a package block decides. Declaring the same module in two files is not enough on its own: compile one of them alone and the other's declarations are E50001 'not resolved'. See Q1380. 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\nYou ALSO need a package as soon as your code spans more than one file, library or not - it is the package block (includeFiles, or applyStandardIncludes) that tells the compiler which files form the build. See Q1380.\n\nWhen you do NOT need a package:\n- Your whole program is a SINGLE file\n- Your code has no external dependencies\n- You are writing scripts or one-file 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. See Q1380 for splitting code across multiple files.","ek9Example":"#!ek9\ndefines 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":["package vs module","module namespace","package distribution"],"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, each shown as `moduleName-version`\n\n2. Not Applied: dependencies that were rejected, with reasons, 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":"#!ek9\ndefines 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":["resolved dependencies","-Dp","dependency listing"],"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\nUsed for pre-release or experimental work, for example the version 2.1.3-beta-5 declared as: version as Version: 2.1.3-beta-5\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":"#!ek9\ndefines 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":["semantic versioning","semver","package version"],"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. Each nested block conventionally increases by exactly one level\n   Use 2 more spaces for each nested block. The lexer accepts a larger even jump (for example 2 spaces to 6), but the formatter and every example use one level at a time, so keep to it.\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":"#!ek9\ndefines 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. 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. Both are reported before the program runs, with the line ek9: wrong number of program arguments (exit 9) - or ek9: cannot convert a program argument (exit 10) - first on stderr, so they never read as the program's own 9 or 10.\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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:\nMake the file executable with chmod u+x script.ek9, then run ./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":"#!ek9\ndefines 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\nAll 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\nUnlike 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\nEach .ek9 file contains exactly one defines module declaration. This keeps the mapping between files and modules clear.\n\n4. File structure\nEvery 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\nUse 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":"#!ek9\ndefines 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\nBridges 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":"#!ek9\ndefines 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":["help lookup","-h help","built-in help"],"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?","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":"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 properties and record fields\n  greeting as String: \"Hello\"    A class property is always private; a record field is public\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  protected/private          Access (methods are public by default - writing public is E07280)\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 cleaned on entry by the receiver\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":"#!ek9\ndefines 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 assign-if-unset. 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","arrow","assignment","basics","beginner","create","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","<- operator basics"],"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  Byte, Bytes             Single octet / immutable octet sequence\n  BytesReader             Forward-only cursor for binary parsing\n  BytesBuilder            Mutable octet buffer (endian-aware writes)\n  Digest                  Unkeyed hashes: SHA256 of String/GUID/Bytes\n  HMAC                    KEYED message authentication: SHA256(key, message)\n  Version                 Semantic version numbers\n  Path                    URI/URL path component\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: 9 pure and 7 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":"#!ek9\ndefines 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().today()\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:\nInteger to Float (safe numeric widening)\nCharacter to String (single char to text)\nDate to DateTime (date to full timestamp)\nMillisecond 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  runRisky()\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:\nNo forgotten-case bugs when a new type is added\nNo instanceof chains that grow with every new type\nNo ClassCastException or casting errors at runtime\nEach type handler is a separate, testable method\nThe compiler validates dispatcher consistency at compile time\nForces 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":"#!ek9\ndefines 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    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 as List of Any: [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    reading <- Float()\n    operator #^ as pure\n      <- rtn as String: `${reading}units`\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":"#!ek9\ndefines 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":["promote operator explained","implicit promotion","type promotion cost"],"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: every type has '?'; value types such as Integer, Float and String also carry '$', '==', '<>' and '#?'; the ordering operators (<, >, <=, >=, <=>) exist only where a type defines them (Boolean has no '<', for example). Check 'ek9 -h TypeName' for what each type offers\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 where they make sense. 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":"#!ek9\ndefines 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 - declared, as they share no type but Any\n      items as List of Any: [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":"#!ek9\ndefines 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 ?\nA function then fills it in and returns it:\n  processData()\n    -> inputData as String\n    <- outcome as Outcome: Outcome()\n    outcome.message: `processed ${inputData}`\n    outcome.exitCode: 0\nThe caller accesses fields by name: result.message, result.exitCode (a field that was never given a value is unset, and interpolating an unset value prints nothing). 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":"#!ek9\ndefines 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?\n      require inputData?\n      outcome: Outcome()\n      outcome.message: `processed ${inputData}`\n      outcome.exitCode: 0\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":["multiple variables","declare multiple","tuple unpacking"],"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 (a Dict lookup miss). Note an empty Optional is NOT absent: it is a real object that is present but unset, so use '?:' (unset-or-absent) to default it; '??' only replaces an absent value.\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\nAssign-if-unset :=? 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 :=? assign-if-unset. 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 assign-if-unset accumulation.","ek9Example":"#!ek9\ndefines 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      // Assign-if-unset - 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, :=? assign-if-unset, 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. An unset operand makes the result unset, propagating the 'unknown' state rather than silently treating it as false - unless the LEFT operand already decides the result: 'and' and 'or' short-circuit, so false and X is a set false, and true or X is a set true, whatever X is. With the unset value on the left (unset and false, unset or true), or with xor and ~, the result is unset.\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\")\nOnly the text 'true' (in any case) gives true. Any other set String, 'maybe' included, gives a SET false - no exception is thrown, and the result is not unset. Only an unset String gives an unset Boolean. So Boolean(\"maybe\")? is true and its value is false; validate the text first if 'maybe' must not mean false.\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":"#!ek9\ndefines 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      //Any text other than 'true' gives a SET false, not an unset Boolean\n      stdout.println(`from text: ${fromText?}, invalid set: ${invalid?}, value: ${invalid}`)\n\n      // Assign-if-unset\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 unless a short-circuiting left operand already decides the result (false and X, true or X).","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":"#!ek9\ndefines 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      // Printed, not branched on: whether 09:30 is before NOW depends on the wall clock, so a bare\n      // 'if' prints nothing at all before 09:30 and the expected output then cannot match.\n      stdout.println(`Morning before now: ${morning < currentTime}`)","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: 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() - each returns the whole duration expressed in that unit (P2Y3M... has months() of 27, not 3). 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":"#!ek9\ndefines 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      // === TOTAL IN EACH UNIT ===\n      // Each accessor returns the WHOLE duration expressed in that unit (months() is 27 here, not 3)\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?","How do I use named capture groups?","How do I do a regex replace with group references?"],"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(Single quotes delimit a Character, so text is always double-quoted.)\n\nSPLITTING STRINGS\nSplit with regex delimiter, works both directions:\n  colonDelimited <- \"one:two:three\"\n  colonRegEx <- /:/\n  parts <- colonDelimited.split(colonRegEx)\n  alsoParts <- colonRegEx.split(colonDelimited)\nBoth produce List of String. (Bind the receiver to a variable first: a method call directly on a literal does not parse.)\n\nCAPTURE GROUPS\ngroup() is the extractor - the equivalent of Java's Matcher.group(), Python's match.group(), JS match() and Go's FindStringSubmatch. It finds the FIRST match anywhere in the input and returns the capture groups as a List of String, in pattern order - the whole match is NOT included (wrap the pattern in (...) if you want it). Works both directions, like split:\n  fields <- dueLine.group(/(\\d{4})-(\\d{2})-(\\d{2})/)\n  year <- fields.getOrDefault(0, \"?\")\nNo match is UNSET - the absence of groups - so the guard form runs only on a match:\n  if parts <- line.group(isoDate)\n    year <- parts.getOrDefault(0, \"?\")\nA match on a pattern with no capture groups is a set, EMPTY list, so the two are distinguishable. An optional group that did not take part is an UNSET element at its index, never a dropped one. group() is a find, not a whole-input test: 'matches' can be false on the same input. Anchor with ^ and $ when you want the whole input.\n\nNAMED GROUPS\nName a group with (?<name>...) and ask for it by name - one String, unset on no match, no such name, or a group that did not take part:\n  year <- dueLine.group(/(?<year>\\d{4})-(?<month>\\d{2})/, \"year\")\n\nREPLACE\nreplace() replaces EVERY match; the replacement can refer to groups as $1 or ${name} - a group name is written in a backtick String, `\\${day}`, because a double-quoted String cannot hold ${ (E07901):\n  iso <- dueLine.replace(/(\\d{2})\\/(\\d{2})\\/(\\d{4})/, \"$3-$2-$1\")\nNo match leaves the text as it was (set). A reference the pattern cannot satisfy ($4 with three groups) is unset rather than an exception. Both work in either direction, like split.\n\nSPLIT IS A DELIMITER SPLIT\nsplit() removes the MATCHED text and returns what surrounded it - capture groups play no part in it. It is keep-all (Java's limit -1), so the empty pieces at either end are retained rather than silently dropped:\n  aroundDigits <- text.split(/\\d+/)\nSplitting on the COMPLEMENT and selecting a piece still works for one self-delimiting field, but group() is the tool for extraction.\n\nNO-MATCH SHAPES\nsplit never found the delimiter: the whole string back as ONE element, set. group never found the pattern: UNSET. Different on purpose - a split always has something to hand back, an extraction does not.\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 semantics.","ek9Example":"#!ek9\ndefines 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\n      // NOTE: $ on a List renders the whole COLLECTION and quotes its String elements\n      // (see the Split: line above) - it is not a join, so check the elements instead.\n      // There is no unchecked indexed get: getOrDefault makes the missing case explicit.\n      require length fromString == 5\n      require fromString.getOrDefault(0, \"?\") == \"one\"\n      require fromString.getOrDefault(4, \"?\") == \"five\"\n      require fromString contains \"three\"\n\n      // === CAPTURE GROUPS ===\n\n      // group() is the extractor: the capture groups of the FIRST match anywhere in the\n      // input, in pattern order. The whole match is not an element - index 0 is group 1.\n      text <- \"This is a sample Text 1234 with numbers in between.\"\n      numberInContext <- /Text (\\d+) with/\n      captured <- text.group(numberInContext)\n      require length captured == 1\n      justNumber <- captured.getOrDefault(0, \"?\")\n      require justNumber == \"1234\"\n      stdout.println(`Extracted number: ${justNumber}`)\n\n      // Several fields from one pattern come back aligned with its groups\n      isoDate <- /(\\d{4})-(\\d{2})-(\\d{2})/\n      dueLine <- \"Due 2024-06-15 at noon\"\n      fields <- dueLine.group(isoDate)\n      require length fields == 3\n      year <- fields.getOrDefault(0, \"?\")\n      month <- fields.getOrDefault(1, \"?\")\n      day <- fields.getOrDefault(2, \"?\")\n      stdout.println(`Date fields: ${year}, ${month}, ${day}`)\n\n      // group() is a find, not a whole-input test - 'matches' is false on that same line\n      require dueLine not matches isoDate\n\n      // No match is UNSET - the absence of groups - so the guard form runs only on a match\n      noDateHere <- \"No date on this line.\"\n      noFields <- noDateHere.group(isoDate)\n      require not noFields?\n      if guarded <- noDateHere.group(isoDate)\n        stdout.println(`Unexpected: ${guarded}`)\n      else\n        stdout.println(\"No match, group is unset\")\n\n      // === NAMED GROUPS ===\n\n      // (?<name>...) names a group; ask for it by name and get one String\n      namedDate <- /(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})/\n      require dueLine.group(namedDate, \"year\") == \"2024\"\n      require not dueLine.group(namedDate, \"hour\")?\n      stdout.println(`Named month: ${dueLine.group(namedDate, \"month\")}`)\n\n      // === REPLACE ===\n\n      // Every match is replaced; $n and ${name} refer to the groups - a name as \\${name} in backticks\n      require dueLine.replace(namedDate, \"$3/$2/$1\") == \"Due 15/06/2024 at noon\"\n      reordered <- dueLine.replace(namedDate, `\\${day}.\\${month}.\\${year}`)\n      stdout.println(`Reordered: ${reordered}`)\n      require noDateHere.replace(namedDate, \"X\") == noDateHere\n      require not dueLine.replace(namedDate, \"$4\")?\n\n      // === SPLITTING IS NOT EXTRACTING ===\n\n      // split() is a DELIMITER split: the matched text is REMOVED and what surrounded it\n      // is returned. Capture groups in the pattern play no part in a split.\n      digitRun <- /\\d+/\n      aroundDigits <- text.split(digitRun)\n\n      stdout.println(`Around the digits: ${aroundDigits}`)\n      require length aroundDigits == 2\n      require aroundDigits.getOrDefault(0, \"\") == \"This is a sample Text \"\n      require aroundDigits.getOrDefault(1, \"\") == \" with numbers in between.\"\n\n      // The fallback before group() existed: split on the COMPLEMENT and pick the piece.\n      // Keep-all retains the empty pieces either side. Fine for one self-delimiting field;\n      // for anything with several fields, alternation or optional parts, use group().\n      nonDigitRun <- /\\D+/\n      pieces <- text.split(nonDigitRun)\n      require length pieces == 3\n      require pieces.getOrDefault(0, \"?\") == \"\"\n      viaComplement <- cat pieces | skip 1 | head 1 | collect as String\n      require viaComplement == justNumber\n      stdout.println(`Via the complement: ${viaComplement}`)\n\n      // Convert extracted text to Integer\n      asInteger <- Integer(justNumber)\n      require asInteger == 1234\n      stdout.println(`As integer: ${asInteger}`)\n\n      // === NO MATCH IS A ONE-ELEMENT LIST, NOT UNSET ===\n\n      // A List is set the moment it exists, so 'delimiter never found' hands back the\n      // whole string as a single element. It does NOT come back unset.\n      noNumbers <- \"No digits here at all.\"\n      noMatch <- noNumbers.split(digitRun)\n      require noMatch?\n      require length noMatch == 1\n      require noMatch.getOrDefault(0, \"\") == noNumbers\n      stdout.println(`No match, single element: ${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. Capture groups: group() is the Matcher.group() / re.search().group() / JS match() / Go FindStringSubmatch equivalent - first match, the groups only (no whole-match element), UNSET on no match so the guard form applies. group(name) reads one named group; replace() is replaceAll with $1 / ${name} references (Java Matcher.replaceAll, Python re.sub, JS replace with $1). split() is a delimiter split only and capture groups play no part in it. A no-match split yields the whole string as one element, never an unset List.","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":"E07901","correct":"reordered <- dueLine.replace(namedDate, `\\${day}.\\${month}.\\${year}`)","incorrect":"reordered <- dueLine.replace(namedDate, \"${day}.${month}.${year}\")","explanation":"A double-quoted String cannot hold ${ (E07901). A named-group template is written in a backtick String with the dollar escaped, so the characters ${day} reach the regex as a group reference. See ek9 -h E07901 for details."},{"error":"E11032","correct":"digitPattern <- /\\d+/","incorrect":"digitPattern <- /\\d+/\n      matches <- digitPattern\n      stdout.println(`Digit match: ${matches}`)","explanation":"EK9 rejects variable names that shadow operator keywords (E11032). 'matches' is an operator keyword, so it cannot name a variable. Use descriptive names like 'digitPattern' or 'emailPattern'. See ek9 -h E11032 for details."},{"error":"E50060","correct":"year <- fields.getOrDefault(0, \"?\")","incorrect":"year <- fields.get(0)","explanation":"group() returns a List of String, and a List has no unchecked get(index) - the out-of-range case must be stated. Use getOrDefault(index, fallback); the fallback is also what you see when the pattern has fewer groups than you indexed. Index 0 is the FIRST capture group - the whole match is not an element. See ek9 -h E50060 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":"#!ek9\ndefines 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, so 99.51#GBP / 2 is 49.755 which 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, not Money: 935.60#GBP / 155.93#GBP is 6.0001.\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":"#!ek9\ndefines 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(\"km\"): name the TARGET unit and the factor is table-driven (1 mile is 1609.344 m by definition) - eightMiles.convert(\"km\") is 12.874752km. There is deliberately no convert(Dimension): a bundled magnitude that is never read taught callers that the argument supplied the factor. A conversion with no table factor (px to m, % to in) is unset.\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":"#!ek9\ndefines 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      // Name the target unit; the factor is table-driven (1 mile = 1609.344 m by definition)\n      inKM <- eightMiles.convert(\"km\")\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(\"km\")","incorrect":"eightMiles.convert(1.609344km)","explanation":"There is no convert(Dimension). Conversion is table-driven, so it takes only the target UNIT as a String - the magnitude of a Dimension argument was never read, and 1.609344 merely happened to equal the table factor. Pass the unit name, or use convert(factor, unit) to supply your own factor. See ek9 -h E50060 for details."}],"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}`)\nOnly backticks interpolate: \"Welcome to ${lang}\" is a compile error (E07901), and `\\${lang}` keeps the characters ${lang}.\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":"#!ek9\ndefines 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\nA set character has length 1. The length of an unset character is itself unset (not 0), so printing it prints nothing:\n  length letter          returns 1\n  letter.length()        method call syntax\n  length Character()     unset\n\nHASHCODE\n  hashValue <- #? letter               Prefix syntax\n  methodHash <- 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":"#!ek9\ndefines 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      // The length of an unset character is unset, not 0\n      unsetLength <- length unsetChar\n      stdout.println(`Unset length isSet: ${unsetLength?}`)\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      // :=: copies one character into 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: there is no binary Integer literal. 0b1010 is a Bits literal, not an Integer (binary as Integer: 0b1010 is E50030, types are not compatible), and printing it shows the bits, 1010, not 10. See Q40 for Bits.\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":"#!ek9\ndefines 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      big <- 1_000_000\n\n      stdout.println(`Decimal: ${decimal}`)\n      stdout.println(`Hex 0xFF: ${hex}`)\n\n      //0b10101010 is a Bits literal, not an Integer - it prints as bits, not 170\n      bitPattern <- 0b10101010\n      stdout.println(`Bits 0b10101010: ${bitPattern}`)\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":["Millisecond type","millisecond timing","Millisecond Duration"],"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":"#!ek9\ndefines 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":["Path type","data navigation path","json path"],"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?","How do I write a literal ${ in an EK9 string?","Why is \"${name}\" in double quotes a compile error 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\nTHE CHARACTERS ${ THEMSELVES\nA double-quoted string cannot hold ${ at all: it would never be interpolated, so the compiler rejects it (E07901) and the error spells out both backtick rewrites - one that interpolates, one that keeps the characters. A backslash is not an escape for $ in a double-quoted string (\"\\${name}\" keeps the backslash).\n\nTo keep the characters ${ as text, write them in a backtick string with the dollar escaped:\n  stdout.println(`Write \\${name} to interpolate a variable`)\n  //Output: Write ${name} to interpolate a variable\n\nThe everyday case is a regular expression replacement that names a group - it needs the characters ${day}, not a variable:\n  dueLine.replace(namedDate, `\\${day}.\\${month}.\\${year}`)\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\nEscape    Double-quoted    Backtick\n${        E07901 error     starts interpolation\n\\$        not an escape    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. And ${ belongs only in a backtick string - in a double-quoted one it is a compile error (E07901).\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":"#!ek9\ndefines 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      // \\${ keeps the characters ${ as text - a double-quoted String cannot hold them (E07901)\n      stdout.println(`Write \\${name} to interpolate a variable`)\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. Kotlin, Groovy, Dart, PHP and the shells interpolate DOUBLE quotes, so \"${name}\" is the habit to unlearn: in EK9 it is a compile error (E07901), never silently the characters.","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."},{"error":"E07901","correct":"stdout.println(`Write \\${name} to interpolate a variable`)","incorrect":"stdout.println(\"Write ${name} to interpolate a variable\")","explanation":"A double-quoted String cannot hold ${ - it never interpolates, so the compiler rejects it (E07901) rather than print it. For the characters themselves write \\${ in a backtick String; a backslash is not an escape for $ in a double-quoted String. See ek9 -h E07901 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":"#!ek9\ndefines 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(money: thirtyDollars, showSymbol: true, showFractionalPart: false)}`)\n      stdout.println(`No symbol, with fraction: ${enGB.format(money: 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":"#!ek9\ndefines 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","size","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":"#!ek9\ndefines 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\nOnly '?' (or a declaration guard) tells the compiler it is safe to call .get(): a check such as 'item is not empty' is not a guard, and .get() after it is rejected (E08030).\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":"#!ek9\ndefines 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- `r?` checks isOk\n- `r.isOk()` is the explicit ok check\n- `r.isError()` is the explicit error check\n- `r is empty` means 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":"#!ek9\ndefines 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 set: ${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 above 45 (E11010), nesting depth above 6 (E11011) and a statement count above 150 for a function (E11012) are rejected, 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":"#!ek9\ndefines 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_new_construct","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 assign-if-unset 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    ->\n      a as Integer\n      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    ->\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\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 ':=?' (assign-if-unset) 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. ':=?' never fails at run time: if the target is already set the assignment is silently skipped, so the FIRST value kept wins (the compiler rejects a repeat it can prove, E08095, but in a loop the first value simply stays). This gives first-assignment-wins semantics in pure functions.\n\nUNSET RETURN AS SIGNAL\nAnother powerful pattern uses an unset return to signal 'not found':\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\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":"#!ek9\ndefines 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 :=? assign-if-unset\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 assign-if-unset 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 assign-if-unset. Go: named returns exist but return statement still required, bare return uses named values but is considered bad practice. C#: return statement required, no assign-if-unset. Kotlin: return statement or last expression, no named return variable, no assign-if-unset. Swift: return statement or implicit single-expression return, no named return, no assign-if-unset. EK9: NO return statement at all, named return variable with '<-', compiler verifies all paths initialise, '?' suffix for uninitialised declaration, ':=?' assign-if-unset 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      premiumThreshold <- 80\n      standardThreshold <- 60\n      if score >= premiumThreshold\n        rating:=? \"Excellent\"\n      else if score >= standardThreshold\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 E08050 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    ->\n      x as Float\n      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  defines function\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\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":"#!ek9\ndefines 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":["abstract functions","abstract signature"],"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":"#!ek9\ndefines 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":"#!ek9\ndefines 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."},{"error":"E01030","correct":"configured <- (multiplier: 2.0, offset: 5.0) is mathOperation as pure function\n        result:=? x * multiplier + offset","incorrect":"configured <- (mathOperation: 2.0, offset: 5.0) is mathOperation as pure function\n        result:=? x * mathOperation + offset","explanation":"A NAMED capture '(name: value)' is a new variable, so like any variable it may not reuse the name of a function (here the module function 'mathOperation') or a type. Rename the capture. An unnamed capture '(scaleFactor)' is checked where 'scaleFactor' was declared. See ek9 -h E01030 and ek9 -q 1392."}],"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  defines function\n    mathOperation() as pure abstract\n      ->\n        x as Float\n        y as Float\n      <- result as Float?\n\n    add() is mathOperation as pure    //Must be pure — compiler enforces\n      ->\n        x as Float\n        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  defines function\n    ReadOnlyConsumer() of type T as pure abstract    //PURE — cannot mutate (this is the shape of the built-in Consumer)\n      -> t as T\n\n    MutatingAcceptor() of type T as abstract    //NOT pure — can mutate (this is the shape of the built-in Acceptor)\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 Result has both whenOk(Consumer) and whenOk(Acceptor), and Optional both whenPresent(Consumer) and whenPresent(Acceptor): the Consumer form is read-only, the Acceptor form may mutate or perform I/O.\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    ->\n      t1 as T\n      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 ':=?' assign-if-unset 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":"#!ek9\ndefines 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  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      // Acceptor is NOT pure, so it may do I/O such as printing - Result.whenOk accepts one\n      reporter <- () is Acceptor of String as function\n        stdout <- Stdout()\n        stdout.println(`ok: ${t}`)\n      r1 <- Result(\"Steve\", Integer())\n      r1.whenOk(reporter)\n\n      // Consumer is pure - read-only access, so it can only inspect (here: validate) the value\n      checker <- () is Consumer of String as pure function\n        require t?\n      opt <- Optional(\"Hello\")\n      opt.whenPresent(checker)\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":["pure functions","Consumer Acceptor","as pure"],"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":"#!ek9\ndefines 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(text: \"Hello\")}`)\n\n      // Delegate — variable holds function reference\n      processor as Processor: UpperProcessor\n      stdout.println(`delegate: ${processor(text: \"Hello\")}`)\n\n      // Reassign delegate\n      processor: LowerProcessor\n      stdout.println(`reassigned: ${processor(text: \"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    -> amount as Float\n    <- op as mathOperation: amount < 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    ->\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\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":"#!ek9\ndefines 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  defines function\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    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\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  customProcessor <- (prefix) is Processor as function\n    result:=? `${prefix} ${text}`\n  custom <- TextHandler(customProcessor)\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":"#!ek9\ndefines 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":["strategy pattern","change behaviour","function field"],"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  defines function\n    transformer() of type T as abstract\n      -> item as T\n      <- result as T?\n\nMULTI TYPE PARAMETER\n  defines function\n    mapper() of type (S, T) as abstract\n      -> item as S\n      <- result as T?\n\nA generic function with no body is declared 'as abstract' (a bodiless 'as open' one is E07110, and an abstract one cannot initialise its return). The abstract template is then implemented 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  defines function\n    shapeHandler() of type T constrain by Shape as open\n      -> shape as T\n      <- result as Boolean: shape?\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\nThe declaration of a named function does not accept a parameterised super: 'doubleInteger() is transformer of Integer' is a syntax error (the 'of' is not expected after 'is'). Create the implementation as a dynamic function, as shown above, and keep it in a variable.\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":"#!ek9\ndefines 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 abstract' for a bodiless template ('as open' only when it has a body), 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":"#!ek9\ndefines 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":"Writing '| call lookup' where a transformation is needed is rejected with E07880 (a function is not expected there), followed by E07830 for the broken pipeline. Use 'map with' to apply a function that takes a parameter and returns a value. See ek9 -h E07880 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: \"Unknown\"\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    ->\n      s1 as Shape\n      s2 as Shape\n    <- result as String: \"generic\"\n\n  intersect()\n    ->\n      s1 as Circle\n      s2 as Circle\n    <- result as String: \"circle-circle\"\n\n  intersect()\n    ->\n      s1 as Circle\n      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":"#!ek9\ndefines 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":["function dispatching","dispatcher keyword","runtime dispatch"],"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. Writing this is not allowed:\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":"#!ek9\ndefines 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 guards 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":"#!ek9\ndefines 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":["else if chain","chain conditions","elsif"],"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, and an unset value. It is REQUIRED: every switch statement needs a default (E07320), and so does every switch expression (E07330). That includes a switch on an enumeration that lists every value, because the value switched on may be unset:\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":"#!ek9\ndefines module qa.flow.branching\n\n  defines function\n\n    supplyStatusCode()\n      <- rtn <- 2\n\n    supplyCommands()\n      <- rtn as List of String: [\"greet\", \"farewell\", \"dance\"]\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      for command in supplyCommands()\n        switch command\n          case \"greet\"\n            stdout.println(\"Hello!\")\n          case \"farewell\"\n            stdout.println(\"Goodbye!\")\n          default\n            stdout.println(`Unknown command: ${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, default required on every switch (E07320/E07330), 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\nWhen the start is greater than the end the range counts downward - no step is needed:\n  for i in 10 ... 0\n    stdout.println($i)\nThis counts from 10 down to 0 (the direction is decided at run time by comparing start and end). 'by -1' is optional and explicit; if you do give a step on a descending range it must be negative ('10 ... 0 by 2' runs no iterations).\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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  loopLimit <- 5\n  while counter < loopLimit\n    stdout.println($counter)\n    counter: counter + 1\nThis prints 0, 1, 2, 3, 4. The condition 'counter < loopLimit' 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  maxIterations <- 100\n  while n <= maxIterations\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":"#!ek9\ndefines 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":"'while true' is a constant Boolean condition, which is pointless (E07390), and EK9 has no break statement (E01070). Put the termination test in the loop condition instead of 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  limit <- 5\n  do\n    stdout.println($counter)\n    counter: counter + 1\n  while counter < limit\nThis prints 0, 1, 2, 3, 4. The body executes first, then 'counter < limit' 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  maximum <- 10\n  do\n    stdout.println(\"Runs once\")\n  while threshold < maximum\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":"#!ek9\ndefines 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      // Start from an empty String, which is SET; String() would be unset,\n      // and anything added to an unset String stays unset (and prints nothing)\n      built <- \"\"\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":"E07390 is the constant condition: 'while true' is a Boolean literal in a condition, which is pointless, so EK9 rejects it. The 'break' that such a loop relies on does not exist in EK9 either (E01070). Put the real termination condition in the while clause instead. 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?","Can switch be used as an expression in EK9?","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. 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":"#!ek9\ndefines 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":["Kotlin","assign","branch","case","compute","condition","control","default","error","expression","fallthrough","flow","given","guard","ok","result","return","statement","switch","ternary","value","when"],"primaryTopics":["switch expression","switch return value","switch statement","no fallthrough"],"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 C (MSC17-C) and CWE-484 both catalogue the forgotten break. 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":"#!ek9\ndefines 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, a forgotten break is a known bug source (CERT C MSC17-C, CWE-484). 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":["case multiple values","comma case","switch fallthrough"],"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":"#!ek9\ndefines 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":["switch comparison","case comparison operators","switch less greater"],"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":"#!ek9\ndefines 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":["pattern matching switch","switch matches"],"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":"#!ek9\ndefines 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":["given when","switch synonym","when synonym"],"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":"#!ek9\ndefines 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":["exhaustive enum switch","enum switch check","switch completeness"],"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. Assign-if-unset (:=?): fills an EXISTING variable only while it is unset, then checks it is set\n     if existing :=? getResult()\nTo re-point an existing variable and enter only when the new value is set, make the check explicit: 'if existing := getResult() then existing?'.\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":"#!ek9\ndefines 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","declaration guard"],"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    case 1\n      stdout.println(\"One\")\n\n  switch value <- getResult() then value\n    case 1\n      stdout.println(\"One\")\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":"#!ek9\ndefines 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 declaration guards. Must write: 'var v = getValue(); switch (v) { ... }' as two steps. Variable leaks into outer scope. Kotlin: 'when' blocks cannot combine declaration guard 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 declaration guards 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":["switch guard variables","guard in switch","switch given"],"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 the collection or range is evaluated and before the first iteration. A while loop guard works the same way: it runs once, and only the condition after with/then is re-checked on each iteration. The guard sets up a precondition for the entire loop, and a variable it declares with <- belongs to the loop - it is gone after it.\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 (also run once).","ek9Example":"#!ek9\ndefines 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":["for loop guard","guard in for","for guard variable"],"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?","Is a while loop guard evaluated on every iteration?"],"answer":"In EK9, a while loop can start with a guard: a declaration (or assignment) followed by 'with' (or 'then') and the loop condition. The guard runs ONCE, before the loop, exactly like a for loop guard. It is an entry gate: if it leaves its variable unset, the loop does not run at all. Once the loop is running, only the condition after 'with' is re-checked on each iteration; the guard is not called again.\n\nBASIC WHILE GUARD\nThe guard declares the variable once; the condition controls the iterations:\n  while value <- supplyCounter() with counter > 0\n    stdout.println(`Counter: ${counter} value: ${value}`)\n    counter: counter - 1\nsupplyCounter() is called once. The loop then runs while 'counter > 0' holds.\n\nUNSET GUARD SKIPS THE LOOP\nIf the guard's value is unset, the condition is never checked and the body never runs. No exception is thrown. This is a safe alternative to checking for null before a while loop.\n\nCONSUMING AN ITERATOR\nBecause the guard runs once, it is the place to get an iterator; the condition re-checks it each time:\n  while iter <- names.iterator() then iter.hasNext()\n    stdout.println(iter.next())\n\nLOOPING UNTIL A VALUE BECOMES UNSET\nThe guard is not re-evaluated, so a guard such as 'while item <- source.next() with item?' would test the same first item forever. To poll until a source runs dry, fetch before the loop and again at the end of the body:\n  item <- source.next()\n  while item?\n    process(item)\n    item: source.next()\n\nWHILE EXPRESSION WITH GUARD\nWhile loops can be expressions that return a value; the guard sets up the loop variable once:\n  total <- while counter <- 0 with counter < limit\n    <- rtn <- 0\n    rtn: rtn + counter\n    counter: counter + 1\nIf the guard is unset, the expression evaluates to the initial return value.\n\nTHREE GUARD OPERATORS IN WHILE\nAll of them run once, before the loop:\n  1. Declaration (<-): creates the variable; the loop runs only if it is set\n     while value <- getNext() with condition\n  2. Assignment (:=): assigns an existing variable, NO isSet check; the condition alone decides\n     while existing := getNext() with condition\n  3. Assign-if-unset (:=?): fills an existing variable only while it is unset, and the loop runs only if it is set\n     while existing :=? getNext() with condition\nTo re-point an existing variable and loop only while the new value is set, check it in the condition: 'while existing := getNext() then existing? and (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. See Q81 for while expressions.","ek9Example":"#!ek9\ndefines module qa.flow.guard.whileloop\n\n  defines function\n\n    supplyCounter()\n      <- rtn <- Integer()\n      rtn: 5\n\n    supplyNothing()\n      <- rtn as Integer: Integer()\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: evaluated ONCE, before the loop ===\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      // === UNSET GUARD: the loop does not run at all ===\n\n      attempts <- supplyStart(2)\n      while missing <- supplyNothing() with attempts > 0\n        stdout.println(`Never printed: ${missing}`)\n        attempts: attempts - 1\n      stdout.println(`Unset guard skipped the loop, attempts still ${attempts}`)\n\n      // === CONSUMING AN ITERATOR: get it once, re-check hasNext() each time ===\n\n      names <- [\"Ada\", \"Grace\", \"Alan\"]\n      while iter <- names.iterator() then iter.hasNext()\n        stdout.println(`Name: ${iter.next()}`)\n\n      // === ASSIGNMENT (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. The guard runs once, before the loop (an unset guard skips the loop); only the condition after 'with' is re-checked each iteration.","keywords":["branch","condition","consume","control","declaration","flow","guard","isset","iteration","loop","null-safe","once","poll","safe","terminate","while"],"primaryTopics":["while loop guard","guard in while","while guard variable"],"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":"'while true' is a constant Boolean condition, which EK9 rejects as pointless (and the break such a loop needs does not exist in EK9, E01070). A guard plus a 'with' condition replaces it: the guard runs once, before the loop, and the condition decides each iteration. 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?","Does finally run when 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 the try statement runs. If the guard is unset, the WHOLE try statement is skipped: the body, catch AND finally. The guard gates the statement exactly as it gates an if, switch, for or while.\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 whole statement is skipped: the body does not run, the catch block does not fire (no exception occurred), and the finally block does not run either. Put cleanup that must happen whatever the guard gives outside the guarded try.\n\nGUARD VS EXCEPTION\nThis is the key distinction:\n  Guard unset: Resource unavailable. Whole try statement skipped, finally included. 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\nAN EXCEPTION FROM THE GUARD ITSELF\nThe guard runs once, OUTSIDE the try body. If acquireResource() itself throws, this try's catch does not see it and its finally does not run: the exception goes to the ENCLOSING handler (an outer try, or the caller). Wrap the guarded try in another try when the acquisition itself can fail.\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\nWHEN FINALLY RUNS\nOnce the guard is set and the try statement is entered, the finally block always executes, whether the body completes or throws - the same guarantee as Java's finally. When the guard is unset the statement is never entered, so its finally does not run.\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":"#!ek9\ndefines 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 (the whole try is skipped, finally included) ===\n\n      try conn <- openFailedConnection()\n        stdout.println(\"Body: never printed\")\n      catch\n        -> ex as Exception\n        stdout.println(\"Catch: never printed\")\n      finally\n        stdout.println(\"Finally: never printed\")\n      stdout.println(\"After the unset guard: whole try skipped\")\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)\n\n      // === TRY EXPRESSION WITH UNSET GUARD (the default return value) ===\n\n      fallback <- try conn <- openFailedConnection()\n        <- rtn as String: \"no connection\"\n        rtn: \"connected to \" + conn.url\n\n      stdout.println(\"Fallback: \" + fallback)","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) and skips the whole try statement, finally included; once entered, finally always runs.","keywords":["acquire","branch","catch","condition","control","declaration","error","exception","finally","flow","guard","handle","isset","migrate","null-safe","resource","safe","try"],"primaryTopics":["try guard","guard variables try","try resource"],"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 assign-if-unset operator :=? in EK9?","url":"https://ek9.io/qa/QA0079.html","alternatePhrasings":["How do I assign only if a variable is unset?","What does the assign-if-unset operator :=? do in EK9?","How do I set a default value for an unset variable?","What is conditional assignment in EK9?"],"answer":"The assign-if-unset 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 two assignment operators with different guard semantics:\n  :=  assigns unconditionally (blind assignment)\n  :=? assigns only if TARGET is currently UNSET\n\nThe key difference: :=? checks the LEFT side (target) BEFORE assigning, and when the target is already set it neither assigns nor evaluates the right side. := always evaluates the right side and always stores it - an unset result OVERWRITES a set x. To re-point x and run a block only when the new value is set, check it explicitly: 'if x := f() then x?' (see Q1981).\n\nIN CONTROL FLOW\nThe :=? operator also works in control flow guards:\n  if existing :=? getValue() with existing > 10\n    process(existing)\nHere, getValue() is only called, and existing only assigned, if existing was unset. The body then runs if existing is SET - including when it already held a value and was left alone - and the 'with' condition is 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: ?: is the expression alternative to assign-if-unset for handling unset values (?? only replaces an absent value, not an unset one). See Q286 for first-wins accumulation with assign-if-unset.","ek9Example":"#!ek9\ndefines 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 <- loadFallback()\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":[":=?","assign","assign-if-unset","assignment","branch","chain","condition","conditional","control","default","fallback","fill","flow","initialize","isset","lazy","null-safe","operator","priority","safe","unset"],"primaryTopics":["assign-if-unset operator :=?","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":"Using '<-' again for 'config' re-declares a variable that already exists in the scope (E50050), and EK9 has no return statement (E01072). Declare config once and apply ':=?' for each source: it only assigns while config is still unset, giving a priority chain with no early returns. 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":"#!ek9\ndefines 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":["for-range expression","range accumulate","for range return"],"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":"#!ek9\ndefines 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":["while expression","do-while expression","loop return value"],"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":"#!ek9\ndefines 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":["for-in expression","for-in fold"],"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\nTRY EXPRESSION (see Q1358)\n  value <- try\n    <- rtn as String: \"default\"\n    rtn: loadText()\n  catch\n    -> ex as Exception\n    rtn: \"recovered\"\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. See Q1358 for try expression.","ek9Example":"#!ek9\ndefines 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, do-while, and try 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":["control flow expressions","statement vs expression","expression constructs"],"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 six expression forms (switch, for-range, for-in, while, do-while, try) 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: any comparison or contains involving an unset Optional gives an UNSET Boolean - neither true nor false - so printing it prints nothing.\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\nOn an unset Optional, contains gives an unset Boolean, not false. Check item? first, or print (item contains 42)? to see that it is unset.\n\nCOMPARISON\n  opt1 == opt2                        true if both set with the same value, false if both set with different values\n  opt1 <> opt2                        not equal\nIf either Optional is unset - including two unset Optionals - the result of == and <> is an unset Boolean.\n\nCOPY AND MERGE\n  copied :=: original                 copy\n  target :~: source                   merge: never overwrites a set value\nAn UNSET target takes the value of a set source; a SET target always keeps its own value, whatever the source; an unset source changes nothing. Use :=: (copy) or :^: (replace) to overwrite, or when an unset source must clear the target.\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 gives the string representation\n- $$item gives the JSON representation\n- #? item gives the 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":"#!ek9\ndefines 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      //contains on an unset Optional is unset, so print whether it is set\n      stdout.println(`none contains 10 isSet: ${(none contains minQuantity)?}`)\n\n      // === COMPARISON ===\n\n      stdout.println(`a == b: ${a == b}`)\n      stdout.println(`a <> c: ${a <> c}`)\n      //Comparing with an unset Optional gives an unset Boolean\n      stdout.println(`a <> none isSet: ${(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 - the target is left unchanged\n      target2 <- Optional(5)\n      target2 :~: Optional() of Integer\n      stdout.println(`Merge from unset: ${target2}`)\n\n      // Merge into a set target - the target keeps its own value\n      target2 :~: Optional(9)\n      stdout.println(`Merge into set: ${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":"#!ek9\ndefines 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(\"Unexpected element: \")\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":["optional stream pipeline","stream optional flatten","optional in pipeline"],"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":"#!ek9\ndefines 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":["isOk isError","ternary access"],"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 ($ of an empty Result is unset)\n\nMERGE (:~:)\nFills unset sides from another Result without overwriting:\n  r1 :~: r2\nIdeal for layering defaults.\n\nCOPY (:=:), COMPARISON (== <>)\nCopy: target :=: source. Comparing two EMPTY Results gives an UNSET Boolean (tri-state), not true - there is nothing to compare.\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":"#!ek9\ndefines module qa.result.operations\n\n  defines function\n\n    <?-\n      Returns a Result with ok value set.\n    -?>\n    getOkResult()\n      <- rtn <- Result(ok: \"Steve\", error: 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(ok: \"Steve\", error: 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      // $ of an empty Result is unset, so print the emptiness test instead\n      stdout.println(`asEmpty is empty: ${cleared is empty}`)\n\n      // === MERGE (:~:) ===\n\n      m1 <- Result(ok: \"Steve\", error: 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      // Comparing two empty Results gives an UNSET Boolean, not true\n      e1 <- Result() of (String, Integer)\n      e2 <- Result() of (String, Integer)\n      emptyComparison <- e1 == e2\n      stdout.println(`empty == empty is set: ${emptyComparison?}`)\n\n      // === STRING ($), JSON ($$), HASHCODE (#?) ===\n\n      display <- Result(ok: \"Steve\", error: 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":["whenOk whenError","callbacks"],"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":"okOnly <- Result(ok: \"Steve\", error: Integer())","incorrect":"okOnly <- 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       shallow copy of original into copied (entries copied, elements shared)\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 <- [1, 2, 3]\n  asText <- $numbers         string representation: [1, 2, 3]\n  asJson <- $$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":"#!ek9\ndefines 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  firstEvens <- cat numbers | filter by isEven | collect as List of Integer\n  firstDoubled <- cat numbers | map with doubleIt | collect as List of Integer\n  firstTotal <- 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":"#!ek9\ndefines 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":"evens <- cat numbers | filter by isEven | collect as List of Integer","incorrect":"evens <- cat numbers | filter by doubleIt | collect as List of Integer","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\nWith +, when keys overlap the right-hand side values win.\n:~: merge operator (mutates the left-hand side):\n  dict1 :~: dict2\nMerge never overwrites a set value: a key dict1 lacks is added, a value dict1 holds unset is filled, and a value dict1 holds set is kept (first recorded wins).\n\nCOPY AND REPLACE\n:=: shallow copy (entries are copied, values are shared):\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  asText <- $ages                 string representation\n  asJson <- $$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  dictIsSet <- emptyDict?             // true (set)\n  dictIsEmpty <- 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":"#!ek9\ndefines 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 - but never overwrites: y keeps 2, z is added\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, :=: shallow 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  a <- 12\n  b <- 10\n  a and b = 8,  a or b = 14,  a xor b = 6\nNote that a 0b literal such as 0b11001100 is a Bits value, not an Integer.\nIMPORTANT: Integer does NOT have shift operators. Use the Bits type for shifting, rotation, and variable-width bit sequences. Convert with the bits() method: a.bits() gives 1100 and shifting that left by 2 gives 110000.\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 arithmetic operators also accept a Float argument and give a Float:\n  i <- 4\n  i * 3.5 gives Float 14.0\nInteger comparison operators and compound assignments (< <= > >= == <> += -= *= /=) take an Integer only, so `i < f` with a Float f is E07620 and `i += f` is E50060. Promote with #^ (#^i < f), or put the Float on the left (f < i). See Q25.\n\nUNSET PROPAGATION\nOperations involving unset values return unset:\n  unsetInt <- Integer()\n  propagatedInt <- unsetInt + 1          // is unset\n  unsetFloat <- Float()\n  propagatedFloat <- 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  enGB <- Locale(\"en_GB\")\n  deutsch <- Locale(\"de_DE\")\n  gbText <- enGB.format(1234567)       // gives \"1,234,567\"\n  deText <- deutsch.format(1234567)    // gives \"1.234.567\"\nFloat adds decimal place control:\n  gbPi <- enGB.format(3.14159, 2)       // gives \"3.14\"\n  dePi <- deutsch.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":"#!ek9\ndefines 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      a <- 12\n      b <- 10\n\n      stdout.println(`a and b: ${a and b}`)\n      stdout.println(`a or b: ${a or b}`)\n      stdout.println(`a xor b: ${a xor b}`)\n\n      // For shift operations, convert to the Bits type\n      bitValue <- a.bits()\n      stdout.println(`Bits of a: ${bitValue}`)\n      shifted <- bitValue << 2\n      stdout.println(`Shifted left by 2: ${shifted}`)\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":["factorial","bitwise integer","integer float advanced"],"typicalErrors":[{"error":"E07620","correct":"shifted <- bitValue << 2","incorrect":"shifted <- a << 2","explanation":"Integer does NOT have shift operators in EK9. Use the Bits type for shifting, rotation, and variable-width bit sequences. Convert with the bits() method (count.bits()). See ek9 -h E07620 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 Q541-Q553 for deep-dive timezone coverage including withSameInstant vs withZone, UTC storage, and cross-timezone comparison.","ek9Example":"#!ek9\ndefines 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":["datetime timezone","timezone offset","DateTime utc"],"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 and no semicolons. Properties are always private (no visibility keyword is accepted on them); methods and constructors are public by default (writing 'public' is E07280) and 'private' or 'protected' restrict them. 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()\n      default operator ?\nA class with properties needs 'default operator ?' (or its own 'override operator ?') to define when it is set. Properties 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: `${name} ${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":"#!ek9\ndefines 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 always private, 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_new_construct","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 ':=?' (assign-if-unset) 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":"#!ek9\ndefines 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":"A pure constructor cannot delegate with this(...) to a constructor that is not pure, which triggers E08130. (The related rule - if ANY constructor is marked 'as pure', ALL must be pure - is also broken here; the compiler reports that too.) Also note pure constructors use ':=?' not ':' for field assignment. See ek9 -h E08130 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_new_construct","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\n      default operator ?\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\n      default operator ?\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() abstract\n        -> input as String\n        <- rtn as String?\n      default operator ?\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":"#!ek9\ndefines 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: a type declares its sameness ONCE - '<=>' (ordered) or '==' (equatable only) - with a matching '#?'; the compiler generates the rest (==, <>, <, <=, >, >= from '<=>'; <> from '=='). Writing those by hand is E07181. Sameness is declared once per hierarchy: here the trait Measurable declares '<=>' and '#?' and Temperature inherits them. 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":"#!ek9\ndefines 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        <- rtn as Integer: #?measure()\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      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      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      //It adds fields, so it declares its own '$' (E07238) - here, deliberately, the super's representation.\n      override operator $ as pure\n        <- rtn as String: $super\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\n        <- rtn as Boolean: degrees? and scale?","incorrect":"operator ? as pure\n        <- rtn as Boolean: degrees? and scale?","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'. Comparisons 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, guards, 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":"<- rtn as Boolean: super? and precision?","incorrect":"<- rtn as Boolean: super? and decimalPlaces?","explanation":"PreciseTemperature declares the field 'precision'. Referring to 'decimalPlaces' in the operator names something that was never declared, so it cannot be resolved. Field names must be consistent throughout the class. Triggers E50001. 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), and a concrete record carries at least one field, counting inherited ones (E07237 - see Q2237).\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\n      default operator ?\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":"#!ek9\ndefines 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":"this.y: y\n\n      default operator\n\n  defines class","incorrect":"this.y: y\n\n      getName()\n        <- rtn as String: name\n\n  defines class","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) - declared once; the compiler generates ==, <>, <, >, <=, >= from it (writing them by hand is E07181). Pair it with a #? hashing the same fields.\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:=: copies into an existing record. The generated 'default operator :=:' is SHALLOW - each field then refers to the source's object; the hand-written one below copies each field through its own :=: (a String copies its value, a List gets its own List of the same elements). See Q2100 for deep or selective copies. :~: 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":"#!ek9\ndefines 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      // <=> is the record's sameness: ==, <>, <, <=, >, >= are generated from it\n      operator <=> as pure\n        -> other as Config\n        <- rtn as Integer: host <=> other.host\n        if rtn == 0\n          rtn: port <=> other.port\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: the hand-written :=: above, each field through its own :=: ===\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":["record operators","record default operators","record copy merge"],"typicalErrors":[{"error":"E07290","correct":"      operator <=> as pure","incorrect":"      getDisplay() as pure\n        <- rtn as String: host\n\n      operator <=> as pure","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 guards 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 <=> (or any comparison operator) 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\n      Summer\n      Autumn\n      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":"#!ek9\ndefines 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: 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_new_construct","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":"#!ek9\ndefines 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":["enumeration vs java","java enum","enumeration migration"],"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()\n      default operator ?\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":"#!ek9\ndefines 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 <- \"base\"\n      default operator\n\n    ExtendedRecord extends OpenRecord\n      extra <- \"more\"\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 <- \"root\"\n\n      label() as pure\n        <- rtn as String: label\n\n      default operator\n\n    ExtendedClass extends OpenClass\n      detail <- \"leaf\"\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    radius <- 0.0\nis the same as:\n  Circle is Shape\n    radius <- 0.0\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":"#!ek9\ndefines 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      default operator $\n      default operator $$\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\n      radius <- 0.0\n\n      Circle()\n        -> radius as Float\n        super(\"circle\")","incorrect":"Circle extends Vehicle\n      radius <- 0.0\n\n      Circle()\n        -> radius as Float\n        super(\"circle\")","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_new_construct","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 E07130.\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":"#!ek9\ndefines 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()\n        -> text as String\n        <- rtn as String: `${prefix()}${text}]`","incorrect":"format()\n        -> text as String\n        <- rtn as String: `${prefix()}${text}]`","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":"E05030","correct":"BracketFormatter extends Formatter","incorrect":"BracketFormatter extends HtmlFormatter","explanation":"Only abstract or open classes can be extended. Abstract classes like Formatter are automatically open. Extending a closed class (here HtmlFormatter, which has no 'as open') triggers E05030 — not open to be extended. See ek9 -h E05030 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?","Why does EK9 require an explicit constructor when properties are uninitialized?","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 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\n    default private Connection()\n\n    Connection()\n      ->\n        host as String\n        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  E08060 — property declared with '?' but not assigned by the constructor, then used (would be NullPointerException in Java)\n  E08070 — property declared with '?' in a class with no constructor at all (reported alongside E07170)\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  MyClass\n    host as String?\n\n    default private MyClass()\n\n    MyClass()\n      -> host as String\n      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\nE07170: NO CONSTRUCTOR FOR AN UNSET PROPERTY\nA property declared without a value ('name as String?') in a class with no developer-written constructor gives E07170: the compiler cannot generate a constructor that initialises it. Either give it a value ('name <- String()') or write the constructor.\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":"#!ek9\ndefines 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    Settings\n      label <- String()\n      retries <- 3\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?}`)\n\n      // === Every property initialised: no constructor needed (label is present but unset) ===\n\n      settings <- Settings()\n      stdout.println(`Settings isSet: ${settings?}`)","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":["E07170","NullPointerException","analysis","class","compile","constructor","default","explicit","field","flow","initialise","lateinit","migrate","null","object-oriented","property","required","safety","uninitialised","uninitialized","value"],"primaryTopics":["uninitialised properties","explicit constructor required","uninitialised field constructor","constructors","E07170","property initialization"],"typicalErrors":[{"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":"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."},{"error":"E08070","correct":"      label <- String()","incorrect":"      label as String?","explanation":"Changing 'label <- String()' (initialised) to 'label as String?' (uninitialised) in a class with no constructor leaves 'label' never initialised: E08070 is reported on the property, and E07170 follows on the class because the compiler cannot generate a constructor for it. Either initialise the property or add an explicit constructor that sets it. See ek9 -h E08070 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 (they sit in the same class as the dispatcher):\n  defines class\n    Renderer\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 (a handler must be pure exactly when the dispatcher entry is)\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    ->\n      s1 as Shape\n      s2 as Shape\n    <- rtn as Boolean: true\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":"#!ek9\ndefines 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":["method dispatch","double dispatch","as dispatcher"],"typicalErrors":[{"error":"E05120","correct":"      override area()\n        <- rtn as Float: 3.14","incorrect":"      area()\n        <- rtn as Float: 3.14","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":"E05210","correct":"      render()\n        -> triangle as Triangle\n        <- rtn as String: `triangle area: ${triangle.area()}`","incorrect":"      render()\n        -> triangle as Triangle\n        <- rtn as String: `triangle area: ${triangle.area()}`\n\n      render()\n        -> number as Integer\n        <- rtn as String: \"int\"","explanation":"A dispatcher handler's parameter type must be in the base dispatch type's hierarchy. An extra Integer handler in a Shape dispatcher can never be reached because Integer is not a subtype of Shape. This triggers E05210. See ek9 -h E05210 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":"#!ek9\ndefines 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()\n        <- rtn as String: name","incorrect":"display()\n        <- rtn as String: name","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 each implement the same method (two different bodies), override it in the class (one implementation plus abstract declarations of that method is not a conflict - see Q680):\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":"#!ek9\ndefines 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":["multiple traits","class implements traits","trait conflict"],"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\n        <- rtn as Boolean: message?","incorrect":"operator ? as pure\n        <- rtn as Boolean: message?","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":"#!ek9\ndefines 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":["implement trait","trait implementation syntax","class with trait"],"typicalErrors":[{"error":"E05120","correct":"EmailSender with trait of Sender\n      override send()","incorrect":"EmailSender with trait of Sender\n      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":"EmailSender with trait of Sender\n      override send()\n        -> message as String\n        <- rtn as Boolean: true","incorrect":"EmailSender with trait of Sender\n      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_adopt_trait","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 Q763 for inheritance depth limits.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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":["trait vs abstract class","trait or abstract"],"typicalErrors":[{"error":"E05120","correct":"      override speed()\n        <- rtn as Float: topSpeed","incorrect":"      speed()\n        <- rtn as Float: topSpeed","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  defines program\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":"#!ek9\ndefines 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 class\n\n    //An ordinary class: constructed directly, never injected (see the typicalErrors)\n    DataProcessor\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 and expose a public API, so 'protected' is not permitted on their methods. Use 'private' for helpers or leave the default public. 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":"E08160","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 E08160 for details."},{"error":"E50020","correct":"defines component\n\n    Repository as abstract","incorrect":"defines class\n\n    Repository as abstract","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: under 'defines service' the service name is followed by its base path, as in 'Items for :/items'.\n\nURI MAPPING\nThe service name maps to a base URI path. Methods map to sub-paths:\n  listAll() for :/             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 += for :/           POST (add to collection)\n  operator -= for :/{id}       DELETE (remove from collection)\n  operator :~: for :/{id}      PATCH (merge/partial update)\n  operator :^: for :/{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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(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  defines text for \"en\"\n    Greetings\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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\n\n    SimpleAspect extends Aspect","incorrect":"defines component\n\n    SimpleAspect extends Aspect","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 Handler\n    override handle()\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":"#!ek9\ndefines 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":"greeting <- () with trait of Handler\n        override handle()","incorrect":"greeting <- () with trait of Handler\n        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?","What string format does default operator $ produce for a record, e.g. Name(field=value)?"],"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: TypeName(field=value, ...) in declaration order, e.g. Hue(red=255, green=0, blue=0); unset when any field is unset\n  #?   hashcode (combined hash of all fields)\n  ?    isSet (true when all fields are set)\n\nSAMENESS\nThe type's sameness is <=> (or == when a field cannot be ordered); ==, <>, <, <=, >, >= are always generated from it. When requesting operators individually, request 'default operator <=>' - asking for 'default operator ==' or '<>' beside it is E07181.\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":"#!ek9\ndefines module qa.oop.defaultoperator\n\n  defines class\n\n    Hue\n      red <- Integer()\n      green <- Integer()\n      blue <- Integer()\n\n      Hue()\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      //== and <> are generated from <=> - never requested separately\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 <- Hue(255, 0, 0)\n      red2 <- Hue(255, 0, 0)\n      blue <- Hue(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","default string representation"],"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 the sameness (<=> or ==), $, #? and ? are defaulted individually; ==, <>, <, <=, >, >= then come from the sameness. 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, guards 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":"#!ek9\ndefines 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() == config2.setting()","incorrect":"config1.getSetting() == config2.setting()","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":[]}
{"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\nRecords cannot have methods (E07290: only constructors and operators are supported on records), so put fluent builder methods on a class that returns itself:\n  ServerBuilder\n    host <- String()\n    withHost()\n      -> hostName as String\n      <- rtn as ServerBuilder: this\n      host: hostName\nThen chain: builder <- ServerBuilder().withHost(\"localhost\")\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":"#!ek9\ndefines 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(host: \"localhost\", port: 8080, maxConnections: 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":["builder pattern","fluent builder","record builder"],"typicalErrors":[{"error":"E06180","correct":"defines record\n\n    ServerConfig\n      host <- String()","incorrect":"defines class\n\n    ServerConfig\n      host <- String()","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(host: \"localhost\", port: 8080, maxConnections: 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":"#!ek9\ndefines 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\n","incorrect":"describe()\n        <- rtn as String: name\n","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 an Integer that is negative, zero or positive (only the sign matters).\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\nThat '<=>' is the type's sameness: '==', '<>', '<', '<=', '>' and '>=' are all generated from it, so do not write them. If '==' must also look at another field, add it to '<=>' as a tie-breaker. Any other ordering is a comparator function, never a second '<=>'.\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":"#!ek9\ndefines 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      //The sameness: by name, then price as a tie-breaker; == and <> are generated from it\n      operator <=> as pure\n        -> other as Product\n        <- rtn as Integer: name <=> other.name\n        if rtn == 0\n          rtn := 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, guards 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":"#!ek9\ndefines 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      -> item as Integer\n      if item?\n        if ~total?\n          count := 1\n          total :=: item\n        else\n          count++\n          total += item\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":"#!ek9\ndefines 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        -> item as Integer\n\n        if item?\n          if ~total?\n            count := 1\n            total :=: item\n          else\n            count++\n            total += item\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":["collect as","stream aggregate","stream reducer"],"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":"#!ek9\ndefines 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":["flatten nested","flatMap","concatMap"],"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":"#!ek9\ndefines 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":["group aggregate","groupBy"],"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":"| group by regionKey\n        | collect as List of List of Sale","incorrect":"| group by regionKey\n        | 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\n        | collect as List of List of Sale","incorrect":"| group by hasTwoOrMore\n        | collect as List of List of Sale","explanation":"The group function's parameter type must match the stream's element type. hasTwoOrMore accepts a List of Sale, not a Sale, so using it to group a stream of Sale triggers E50030 because the types are incompatible. See ek9 -h E50030 for details."},{"error":"E06300","correct":"| group by regionKey\n        | filter with hasTwoOrMore\n        | collect as List of List of Sale","incorrect":"| group by comparingRegion\n        | filter with hasTwoOrMore\n        | collect as List of List of Sale","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":"grouped <- cat sales\n        | sort by comparingRegion","incorrect":"grouped <- cat sales\n        | 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":"#!ek9\ndefines 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":["stream head","stream tail","stream skip"],"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":"firstThree <- cat items | head 3 | collect as List of String","incorrect":"firstThree <- cat items | head 3 | collect as List of Integer","explanation":"Whatever head, tail or skip lets through, the collect must gather the stream's own element type: a String stream cannot be collected into a List of Integer, so the compiler reports E07830 because there is no pipe between String and Integer. See ek9 -h E07830 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  ok <- Result(value, Integer())          success: ok set, error unset\n  failed <- Result(String(), errorCode)  failure: error set, ok unset\nThe two type parameters must be different types (Result of (String, String) is E06190)\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":"#!ek9\ndefines 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\nELEMENT TYPE UNIFICATION\nThe element types are unified into one type, whatever their order:\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.","ek9Example":"#!ek9\ndefines module qa.collections.typeinference\n\n  defines program\n\n    TypeInferenceDemo()\n      stdout <- Stdout()\n\n      // === INFERRED FROM THE ELEMENTS ===\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: element type unification, 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()\n      -> item as String\n      if 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  ValidatedListReader\n    items as List of String: List() of String\n    size() as pure\n      <- rtn as Integer: length items\n    get() as pure\n      -> index as Integer\n      <- rtn as String: 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":"#!ek9\ndefines 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: 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":"#!ek9\ndefines 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  shorterList <- 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"E07520","correct":"isPositive() as pure\n      -> num as Integer\n      <- rtn as Boolean: num > 0","incorrect":"isPositive() 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, E07520 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 E07520 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":"#!ek9\ndefines 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?","Capture intermediate sorted results into a side list using tee.","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, which items does a tee between them capture?","Tap into a stream pipeline to collect intermediate results without disrupting flow"],"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  copy <- cat items | tee in captured | collect as List of String\nAfter the pipeline, captured contains exactly the items that flowed through that point. Useful for debugging or capturing intermediate state.\n\nTEE IS A LAZY PEEK\nA head bounds the PULL, so a tee before a head records only the items the head demanded: cat items | tee in seen | head 3 leaves 3 items in seen, and cat items | sort | tee in seen | head 3 also leaves 3 (the sort sees everything, the tee only what is emitted past it). To capture every item, put the tee before a stage that must see everything (sort, tail, join) or drop the head.\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 KEEPS THE FIRST OF EACH\nuniq keeps the first occurrence of each element anywhere in the stream and drops every later one that is == to it - global, not just consecutive repeats:\n  cat items | uniq | collect as List of String\nNo sort is needed; add one only to get the result in order:\n  cat items | sort | uniq | collect as List of String\nThe element's #? (hashcode) only narrows the search; == decides.\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 (optional before uniq, only to order the result). See Q124 for group by. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide.","ek9Example":"#!ek9\ndefines 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      // uniq is global first-seen; the sort only orders the result\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":"Unix: cmd | tee file.txt | next. 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 (lazy, like EK9 tee before head); iter.dedup() only drops CONSECUTIVE repeats, unlike EK9 uniq, which is global first-seen like Java distinct(). Go: manual implementation for both. EK9: | tee in variable for capture, | uniq for global dedup by ==, | uniq by func for dedup by key.","keywords":["capture","collection","copy","data-structure","debug","duplicate","effect","inspect","intercept","intermediate","observe","pipeline","side","side list","snapshot","stream","tap","tee","uniq","unique"],"primaryTopics":["stream tee","stream uniq","unique stream items","tee operation","intermediate capture"],"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 | uniq in a pipeline to keep the first of each equal element (add | sort to order the result). 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 | uniq in a pipeline to keep the first of each equal element (add | sort to order the result). 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":"#!ek9\ndefines 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":"E07850","correct":"catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)","incorrect":"catch\n        ->\n          ex1 as Exception\n          ex2 as Exception\n        stdout.println(\"Error caught\")","explanation":"EK9 catch blocks accept only a single exception parameter. Unlike Java's multi-catch, you cannot list several exception variables. E07850 is triggered — only a single Exception is supported. Catch the common base type (Exception) and check specific types within the block. See ek9 -h E07850 for details."},{"error":"E04030","correct":"catch\n        -> ex as Exception\n        stdout.println(\"Caught: \" + ex.reason())","incorrect":"catch\n        -> ex as String\n        stdout.println(\"Caught: \" + ex)","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 once the try statement is entered, whether the try block succeeds or an exception is thrown and caught.\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 ONCE THE TRY IS ENTERED\nFinally executes in both of these scenarios:\n1. Try completes normally (no exception)\n2. Exception thrown and caught\nThis guarantee makes finally ideal for cleanup operations.\n\nAN UNSET GUARD SKIPS EVERYTHING\nA try with a guard (try value <- getValue()) is only entered when the guard is set. When the guard is unset the whole statement is skipped: the try body, the catch AND the finally. Finally guarantees cleanup for a statement that was entered, not for one that never started.\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":"#!ek9\ndefines 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 the same guarantee once the try is entered; with a guard variable, an unset guard skips the whole statement, finally included.","keywords":["always","catch","cleanup","defer","error","exception","finally","guarantee","handle","nested","pattern","resource","swift","try"],"primaryTopics":["try catch finally","finally cleanup"],"typicalErrors":[{"error":"E07850","correct":"catch\n        -> ex as Exception\n        stdout.println(\"Catch: \" + $ex)","incorrect":"catch\n        ->\n          ex1 as Exception\n          ex2 as Exception\n        stdout.println(\"Catch: failed\")","explanation":"Catch blocks accept only a single exception parameter. EK9 does not support multi-catch syntax like Java. E07850 — only a single Exception is supported. Use the Exception base type and dispatch inside the catch body. See ek9 -h E07850 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":"result <- riskyOperation(false)\n        stdout.println(\"Try: \" + result)","incorrect":"result <- riskyOperation(false)\n        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\", 3)\nRead the exit code with ex.exitCode(). If the exception is never caught, the program prints it on stderr (Exception: Fatal error: Exit Code: 3), runs its finally blocks and teardown, and ends with that code as the process exit status - 3 here. An uncaught Exception with no exit code ends with status 1. Only the exception that reaches the top counts: Exception(\"Wrapped\", cause) exits 1 even when the cause carries a code - give the wrapper its own code, Exception(\"Wrapped\", cause, 3), to keep it.\n\nTHROW KEYWORD\nThrow an exception variable:\n  ex <- Exception(\"Error occurred\")\n  throw ex\nOr throw the result of a constructor or function call directly:\n  throw Exception(\"Error occurred\")\nThe throw statement takes either a variable or a call.\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 ?\n    default operator $\n    default operator $$\nCustom exceptions can carry additional context beyond the reason string. A type that adds a field declares its own '$' and '$$' (E07238), so the field shows in its string and JSON.\n\nEXCEPTION PROPERTIES\nAll exceptions have:\n  - ex.reason() gives the error message\n  - ex.exitCode() gives the optional exit code\n  - $ex gives the string representation\n  - ex? is the 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. See Q1432 for ending a program with a chosen exit code. See Q1434 for Panic, which only the language raises.","ek9Example":"#!ek9\ndefines 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      default operator $\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 (a variable) or throw Exception(\"msg\") (a call, no 'new' keyword), 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":"#!ek9\ndefines 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":"resource <- ManagedResource(\"config.txt\")\n        content <- resource.read()","incorrect":"resource <- ManagedResource(\"config.txt\")\n        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":"resource <- openResource(\"database\")\n        content <- resource.read()\n        stdout.println(\"Read: \" + content)","incorrect":"resource <- openResource(\"database\")\n        content <- resource.read()\n        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    handleTimeout(ex)  // 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":"#!ek9\ndefines 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      default operator $\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      default operator $\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":["catch specific exception","exception subtype catch","typed catch"],"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":"#!ek9\ndefines 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":["try catch vs result","error handling strategy","exception or result"],"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\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.2km\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. Names are case-sensitive, so a reference must match the declaration exactly.\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":"#!ek9\ndefines 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 (any type, as long as the initialiser is a const expression). 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":"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":"The constant is declared as 'maxRetries'; EK9 is case-sensitive, so MAX_RETRIES is a different, undeclared name. 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_new_construct","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":["cross-module constants","shared constants"],"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 (any type is allowed, including structs and arrays, as long as the initialiser is a const expression). '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":"#!ek9\ndefines 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 (const-expression initialisers), 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 :=? assign-if-unset still mutates the target when it is unset. EK9 constants cannot be mutated through any mechanism including assign-if-unset. 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\nApple SSL bug (2014, CVE-2014-1266): a duplicated goto statement bypassed SSL certificate validation on iOS and macOS. Linux kernel (2019): auditing its switch statements so that -Wimplicit-fallthrough could be turned on found and fixed a number of missing break statements. CERT C recommendation MSC17-C: finish every case with a break - an omitted one falls through silently (CWE-484).\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    threshold <- 100\n    if number > threshold\n      label: \"high\"\nMultiple case values replace switch fallthrough:\n  switch day\n    case \"Saturday\", \"Sunday\"\n      type: \"weekend\"\n    default\n      type: \"weekday\"\nDeclaration guards 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":"#!ek9\ndefines 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 guards.","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":"#!ek9\ndefines 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":["skip iterations","break continue"],"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 <- false\n    maxAge <- 150\n    valid: age >= 0 and age <= maxAge\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    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\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":"#!ek9\ndefines 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, guards 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 declaration guard: 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 lets an omitted break compile silently (CERT C MSC17-C, CWE-484). 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. The CERT C Coding Standard has a recommendation against it (MSC17-C) and it is catalogued as CWE-484. 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 SWITCHES\nWith enumerations you list the values, but a switch statement still needs 'default' even when every value is covered - it handles the unset value (E07320 without it):\n  switch colour\n    case Hue.Red\n      label: \"stop\"\n    case Hue.Amber\n      label: \"caution\"\n    case Hue.Green\n      label: \"go\"\n    default\n      label: \"unknown\"\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":"#!ek9\ndefines module qa.flow.philosophy.nofallthrough\n\n  defines type\n    Hue\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 <- [Hue.Red, Hue.Amber, Hue.Green]\n      for colour in colours\n        meaning <- String()\n        switch colour\n          case Hue.Red\n            meaning: \"stop\"\n          case Hue.Amber\n            meaning: \"caution\"\n          case Hue.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":["switch fallthrough","group cases","fallthrough bug"],"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":"A switch statement requires a default clause to handle unexpected values. Without it, the compiler reports E07320. This ensures every possible value is handled. Enum switches also require default, even with every value covered, because it handles the unset value (see Q73). 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":"#!ek9\ndefines 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, guards, 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 the 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":"#!ek9\ndefines 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":["money currency safety","built-in money","money design"],"typicalErrors":[{"error":"E50060","correct":"mixedResult <- tenPounds + thirtyDollars","incorrect":"mixedResult <- 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\nOne method, two separate arguments - the rate and the target currency code:\n  inUSD <- amount.convert(1.32845, \"USD\")   applies the RATE, result in USD\nThe rate is a Float, so it keeps its full precision; the result is rounded to the TARGET currency's precision (149.27#GBP * 1.32845 = 198.30#USD). There is deliberately no convert(Money) form: a Money literal such as 1.32845#USD is rounded to USD's two fraction digits the moment it is created (it holds 1.33), so a Money cannot carry an exchange rate. Keep the rate and the currency apart.\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: £10.00    de_DE: 10,00 £\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":"#!ek9\ndefines 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 == 149.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 == 198.30#USD\n      stdout.println(`Converted: ${totalInUSD}`)\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(rate, currencyCode) - a Float rate that keeps its precision and a separate ISO-4217 code, never a Money as the rate - 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":["currency conversion","money formatting","exchange rate"],"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 the rate as a Float and the target currency as a separate code — the rate is always required, and there is no automatic rate lookup. See ek9 -h E50060 for details."},{"error":"E50060","correct":"totalInUSD <- streamTotal.convert(1.32845, \"USD\")","incorrect":"totalInUSD <- streamTotal.convert(1.32845#USD)","explanation":"There is no convert(Money). A Money literal is rounded to its currency's precision the moment it is created, so 1.32845#USD holds 1.33 and cannot carry an exchange rate. Pass the rate as a Float and the target currency as a separate code. 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 an ABSOLUTE file path as a String or a FileSystemPath:\n  file <- TextFile(\"/tmp/data.txt\")\n  fromPath <- TextFile(FileSystemPath().inCurrentWorkingDirectory(\"data.txt\"))\nA relative name is never resolved against the working directory for you: TextFile(\"data.txt\") is always unset. Anchor it with inCurrentWorkingDirectory (or inTemporaryDirectory) as above.\nThe file is not opened until you call input() or output(). But a TextFile is only SET when its path is absolute, exists and is a file: TextFile of a missing path is unset, so $file, file.isReadable() and file.input() are all unset too (and printing them prints nothing).\n\nMAKING SURE THE FILE EXISTS\nFileSystemPath.createFileIfAbsent() creates an empty file if needed. It returns a Result that must be checked:\n  path <- FileSystemPath().inCurrentWorkingDirectory(\"data.txt\")\n  created <- path.createFileIfAbsent()\n  if created.isOk()\n    file <- TextFile(path)\n\nREADING WITH INPUT STREAM\nUse file.input() to get a CloseableStringInput stream:\n  try\n    -> input <- file.input()\n    if 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, the TextFile is unset and file.input() returns an unset stream. A try-with-resources declaration (try -> input <- file.input()) is NOT a guard: the body still runs with the unset stream. Check input? inside the try (or check file? before it) before reading.\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":"#!ek9\ndefines 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      // === A MISSING FILE GIVES AN UNSET TEXTFILE ===\n\n      missing <- TextFile(\"/tmp/qa-example-not-there.txt\")\n      stdout.println(`Missing file set: ${missing?}`)\n\n      // === MAKE SURE THE FILE EXISTS ===\n\n      // createFileIfAbsent returns a Result that must be checked\n      path <- FileSystemPath(\"/tmp/qa-example-input.txt\")\n      created <- path.createFileIfAbsent()\n      stdout.println(`File ready: ${created.isOk()}`)\n\n      // === CREATING A TEXTFILE ===\n\n      file <- TextFile(path)\n      stdout.println(\"File: \" + $file)\n\n      // === TEXTFILE PROPERTIES ===\n\n      stdout.println(\"Is readable: \" + $file.isReadable())\n\n      // Give the file some content to read back\n      try\n        -> output <- file.output()\n        if output?\n          output.println(\"first line\")\n          output.println(\"second line\")\n\n      // === READING WITH TRY-WITH-RESOURCES ===\n\n      // The try body runs even if input() is unset, so check input? before reading\n      try\n        -> input <- file.input()\n        if input?\n          cat input | map with processLine > stdout\n\n      // === THE BODY STILL RUNS FOR A MISSING FILE ===\n\n      try\n        -> missingInput <- missing.input()\n        stdout.println(`Missing input set: ${missingInput?}`)","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(absolutePath) - anchor a relative name with FileSystemPath().inCurrentWorkingDirectory('file') - with try -> input <- file.input() for automatic resource management; a missing file gives an unset TextFile and an unset stream, so check input? inside the try.","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. Make sure the file exists, create a TextFile for it, open an output stream inside try-with-resources, and write lines using the stream.\n\nTHE FILE MUST EXIST FIRST\nA TextFile is only set when its path exists and is a file. TextFile of a path that does not exist yet is unset, its output() is unset, and nothing is written - no file is created. Create the file first with FileSystemPath.createFileIfAbsent(), which returns a Result that must be checked:\n  path <- FileSystemPath().inCurrentWorkingDirectory(\"output.txt\")\n  created <- path.createFileIfAbsent()\nThe path must be ABSOLUTE: createFileIfAbsent on a relative FileSystemPath(\"output.txt\") returns an error Result (\"path is not absolute\"), and TextFile of a relative name is always unset.\n\nCREATING OUTPUT\nUse file.output() to get a CloseableStringOutput stream:\n  file <- TextFile(path)\n  try\n    -> output <- file.output()\n    if output?\n      output.println(\"Hello, file!\")\nThe output stream is automatically closed when the try block exits. Opening it replaces what the file held.\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 try-with-resources:\n  try\n    -> output <- file.output()\n    if output?\n      output.println(\"data\")\n  catch\n    -> ex as Exception\n    stderr.println(\"Write failed: \" + $ex)\nThe resource declaration is not a guard: if the file cannot be opened for writing, output() returns an unset value and the try body STILL runs. That is why the body checks output? before writing.\n\nSee Q151 for reading files. See Q134 for try/catch. See Q3 for stdout.","ek9Example":"#!ek9\ndefines module qa.fileio.write\n\n  defines program\n\n    WriteTextFileDemo()\n      stdout <- Stdout()\n\n      // === A NEW FILE MUST EXIST BEFORE TEXTFILE IS SET ===\n\n      notYetCreated <- TextFile(\"/tmp/qa-example-never-created.txt\")\n      stdout.println(`Absent file set: ${notYetCreated?}`)\n\n      // createFileIfAbsent returns a Result that must be checked\n      path <- FileSystemPath(\"/tmp/qa-example-output.txt\")\n      created <- path.createFileIfAbsent()\n      stdout.println(`File ready: ${created.isOk()}`)\n\n      // === CREATING A TEXTFILE FOR WRITING ===\n\n      file <- TextFile(path)\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 try body runs even if output() is unset, so check output? before writing\n      try\n        -> output <- file.output()\n        if output?\n          output.println(\"Hello, file!\")\n          output.println(\"Line two\")\n\n      // Two lines: 13 + 9 characters including the newlines\n      stdout.println(\"File length: \" + $(length file))","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: create the file first (FileSystemPath.createFileIfAbsent(), checked), then TextFile(path) with try -> output <- file.output() and an output? check, 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":"#!ek9\ndefines 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(FileSystemPath().inCurrentWorkingDirectory(\"data.txt\"))\n  try\n    -> input <- file.input()\n    if input?\n      cat input > stdout\nEach line from the file flows through the pipeline as a String. A TextFile of a missing path (or a relative one: TextFile(\"data.txt\") is never resolved against the working directory) is unset and so is its input(); the try body still runs, which is why it checks input? first.\n\nMAKING SURE THE FILE EXISTS\nFileSystemPath.createFileIfAbsent() creates an empty file when needed and returns a Result that must be checked:\n  path <- FileSystemPath(\"data.txt\")\n  created <- path.createFileIfAbsent()\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 trimmed > stdout\nThis skips empty lines and trims whitespace from each remaining line. Both stages need functions: notEmpty must test 'line is not empty', because an empty line \"\" is SET and 'line?' would let it through; trimmed wraps the String method trim(), which cannot be named in 'map with' directly:\n  notEmpty() as pure\n    -> line as String\n    <- rtn as Boolean: line is not empty\n  trimmed() as pure\n    -> line as String\n    <- rtn as String: line.trim()\n\nCOLLECT LINES INTO LIST\nGather all lines into a collection:\n  allLines <- 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":"#!ek9\ndefines module qa.fileio.lines\n\n  defines function\n\n    notEmpty() as pure\n      -> line as String\n      <- rtn as Boolean: line is not empty\n\n    trimmed() as pure\n      -> line as String\n      <- rtn as String: line.trim()\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      // createFileIfAbsent returns a Result that must be checked\n      path <- FileSystemPath(\"/tmp/qa-example-lines.txt\")\n      created <- path.createFileIfAbsent()\n      stdout.println(`File ready: ${created.isOk()}`)\n\n      file <- TextFile(path)\n      stdout.println(\"File for line reading: \" + $file)\n\n      // Three lines, the middle one empty\n      try\n        -> output <- file.output()\n        if output?\n          output.println(\"line one\")\n          output.println(\"\")\n          output.println(\"  line three  \")\n\n      // === FILTER AND TRANSFORM ===\n\n      // An empty line \"\" is SET, so notEmpty tests 'is not empty', not '?'\n      try\n        -> input <- file.input()\n        if input?\n          cat input | filter by notEmpty | map with trimmed | map with addLineNumber > stdout\n\n      // === COLLECT INTO LIST ===\n\n      try\n        -> input <- file.input()\n        if input?\n          allLines <- cat input | collect as List of String\n          stdout.println(`Lines collected: ${length allLines}`)\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":["read file lines","file line by line","cat lines"],"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 a program with @Test to make it a test; test programs live in dev source files (a dev/ directory, see Q805), because @Test and assert are compile errors in an ordinary source file (E81015). Group related tests with @Test: \"groupName\".\n\nTHE @TEST DIRECTIVE\nInside a defines program section of a dev source file, place @Test on its own line before a program to mark it as a test:\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 in a dev source file 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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, lockedItem is an incoming parameter, so plain assignment ('lockedItem := ...') is rejected as E08110 (reassignment of an incoming argument). Use the copy operator :=: to copy a new value into the protected item: 'lockedItem :=: lockedItem + amount'.\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":"#!ek9\ndefines 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\nRESULT ORDER AND HEAD\nResults flow on in the order the calls COMPLETE, not the order the functions arrived, so `cat workers | async | head 2` gives the first two to complete and can differ from run to run. Sort afterwards if you need a stable order. When only map, call or tee sit between async and head N, just N calls are started; with a stage that can drop items between them (filter, reject, uniq, skip, flatten) more calls are started, up to a fixed window, and the results not needed are discarded.\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 basics (the Counter field-on-class pattern). See Q160 for concurrency model. See Q1299 for the async + same-lock pitfall. See Q125 for head/tail.","ek9Example":"#!ek9\ndefines 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 MutexLock basics (the Counter\n      // field-on-class pattern) for async-safe shared state.\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 most 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":"#!ek9\ndefines 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":["async await","goroutines"],"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\nReturns a value for any key:\n  age <- ages.getOrDefault(\"Alice\", 0)\nIf the key exists, returns its value. If the key is missing, returns the default you provide. The default covers a MISSING key only - a key that is present returns its stored value, which could itself be unset. No exceptions.\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 a real entry (its value can still be unset if one was stored).\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":"#!ek9\ndefines 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 returns the default when the key is missing. 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). 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\nReturns the default only when the index is out of bounds:\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 is an element of the list; elements can themselves be unset, so check with '?' when that matters.\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":"#!ek9\ndefines 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) returns the default for an out-of-bounds index, 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 returns the default for an out-of-bounds index. 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). 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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      r3b <- getErrorResult()\n      if r3b.isOk()\n        stdout.println(`Dual ok: ${r3b.ok()}`)\n      else if r3b.isError()\n        stdout.println(`Dual error: ${r3b.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, fallback)\n  list.getOrDefault(index, fallback)\n  optional.getOrDefault(fallback)\n  result.okOrDefault(fallback)\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":"#!ek9\ndefines 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":["dict list get","no get method","getOrDefault design"],"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, fallback)\nLookup by key with fallback.\n\nLIST: GETORDEFAULT WITH INDEX\n  list.getOrDefault(index, fallback)\nLookup by position with fallback.\n\nOPTIONAL: GETORDEFAULT\n  optional.getOrDefault(fallback)\nExtract contained value or use fallback.\n\nRESULT: OKORDEFAULT / ERRORORDEFAULT\n  result.okOrDefault(fallback)\n  result.errorOrDefault(fallback)\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 assign-if-unset operator in EK9?","How do I chain fallback values in EK9?"],"answer":"EK9 provides several mechanisms for fallback values: the :=? assign-if-unset, getOrDefault(), and the ternary guard pattern.\n\nASSIGN-IF-UNSET (:=?)\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  ages <- {\"Alice\": 30}\n  numbers <- [10, 20]\n  maybe <- Optional() of Integer\n  fromDict <- ages.getOrDefault(\"Bob\", 0)\n  fromList <- numbers.getOrDefault(5, 0)\n  fromOptional <- maybe.getOrDefault(0)\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 assign-if-unset. See Q84 for Optional ternary guard.\n\nSee Q29 for unset variables. See Q166 for consistent safe pattern. See Q243 for coalescing operators.","ek9Example":"#!ek9\ndefines module qa.safeaccess.fallback\n\n  defines program\n    FallbackValuesDemo()\n      stdout <- Stdout()\n\n      // === ASSIGN-IF-UNSET (:=?) ===\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 assign-if-unset. 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: :=? assign-if-unset 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":"Assign-if-unset :=? 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":"#!ek9\ndefines 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":["whenPresent","whenOk whenError","callback processing"],"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":"#!ek9\ndefines 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":"#!ek9\ndefines 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.includes(\"Brown\")","explanation":"String has no includes() method (a JavaScript habit; indexOf() is missing too). 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"String has a replace() method, but it takes a RegEx (not a String) as the pattern: replace(with as RegEx, replacement as String). It replaces EVERY match, and the replacement can refer to capture groups as $1, $2. For other transformations, use split + stream rejoin, or stream the characters through map/filter.\n\nREPLACE WITH A REGEX\n  spaced <- csv.replace(/,/, \" \")          // \"Alice Bob Charlie\"\n  masked <- code.replace(/[0-9]+/, \"#\")      // \"a1b22c333\" becomes \"a#b#c#\"\nPassing a String as the pattern (csv.replace(\",\", \" \")) does not compile: there is no replace() that takes a String.\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\nWHEN TO STREAM INSTEAD\nStrings are streamable sequences of characters, so per-character rules (upper-case each one, keep only letters) are a map or filter stage rather than a pattern.\n\nSee Q37 for string streaming. See Q33 for regular expressions. See Q89 for stream pipelines.","ek9Example":"#!ek9\ndefines 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      // === REPLACE WITH A REGEX ===\n\n      csv <- \"Alice,Bob,Charlie\"\n      spaced <- csv.replace(/,/, \" \")\n      stdout.println(`Replaced: ${spaced}`)\n\n      // Every match is replaced, not only the first\n      code <- \"a1b22c333\"\n      masked <- code.replace(/[0-9]+/, \"#\")\n      stdout.println(`Masked: ${masked}`)\n\n      // === SPLIT AND REJOIN ===\n\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: str.replace(/regex/, replacement) replaces every match (like Java's replaceAll) - the pattern must be a RegEx literal, not a String; or 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":"spaced <- csv.replace(/,/, \" \")","incorrect":"spaced <- csv.replace(\",\", \" \")","explanation":"String.replace() takes a RegEx as its pattern, not a String, so there is no replace() matching (String, String). Write the pattern as a regex literal: /,/. 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":"#!ek9\ndefines 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":["pad width","fixed width"],"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, and String has no endsWith(). String does have startsWith(prefix), and the matches operator with regex anchors covers both a prefix and a suffix.\n\nSTARTS WITH (PREFIX)\nCall startsWith, or use regex with the ^ anchor:\n  if myString.startsWith(\"Hello\")\n    stdout.println(\"Starts with Hello\")\n  if myString matches /^Hello.*/\n    stdout.println(\"Starts with Hello\")\n\nENDS WITH (SUFFIX)\nThere is no endsWith(), so use regex with the $ 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. startsWith covers a fixed prefix; the matches operator with regex provides the rest (every suffix check, and complex patterns) with more flexibility.\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":"#!ek9\ndefines 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.startsWith(\"Hello\")\n        stdout.println(\"startsWith Hello\")\n\n      startsWithGoodbye <- greeting matches /^Goodbye.*/\n      stdout.println(`Starts with Goodbye: ${startsWithGoodbye}`)\n\n      // === ENDS WITH (SUFFIX) ===\n\n      if greeting matches /.*World$/\n        stdout.println(\"Ends with World\")\n\n      endsWithEarth <- greeting matches /.*Earth$/\n      stdout.println(`Ends with Earth: ${endsWithEarth}`)\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.startsWith(prefix), str matches /^prefix.*/, str matches /.*suffix$/, no endsWith(), hasPrefix() or 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 startsWith(prefix), or the matches operator with regex anchors (^, $), for prefix checks; suffix checks use the $ anchor because there is no endsWith(). 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?","Extract the first and last characters from a string.","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":"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\nOPERATOR FORM\nThe prefix (#<) and suffix (#>) operators give the first and last character too:\n  firstChar <- #< name\n  lastChar <- #> name\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":"#!ek9\ndefines 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","introspection","last","operator","position","prefix","string","suffix","tail","text"],"primaryTopics":["string first last character","string head tail","character extraction","prefix operator","suffix operator"],"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-style fuzzy matching?","How do I find similar strings in EK9?","How do I detect typos or approximate matches in EK9?"],"answer":"EK9 has a built-in fuzzy-match operator for strings. The <~> operator returns a weighted, Levenshtein-style match COST as an Integer: lower means closer, 0 means identical. It is not the plain edit count.\n\nFUZZY MATCH COST (<~>)\n  cost <- str1 <~> str2\nThe weights: a change of case only costs 1; a substitution, an insertion or a deletion usually costs 2. When the two strings share too few characters to be a plausible match (including comparing with \"\"), the result is the Integer maximum, 2147483647, meaning 'no viable match'.\n\nIDENTICAL STRINGS\nComparing \"hello\" with \"hello\" gives 0.\n\nONE SUBSTITUTION\nComparing \"kitten\" with \"sitten\" gives 2.\n\nSEVERAL EDITS\nComparing \"kitten\" with \"sitting\" gives 6 (the classic edit count is 3).\n\nPRACTICAL USE: TYPO DETECTION\nPick a threshold in COST units, not edits. A transposed pair (\"funciton\" for \"function\") costs 4:\n  if (input <~> expected) <= 4\n    stdout.println(\"Close enough\")\n\nPRACTICAL USE: CLOSEST MATCH\nCompare the input with each candidate and keep the lowest cost: \"colr\" scores 2 against \"color\", 4 against \"collar\", and the Integer maximum against \"column\".\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":"#!ek9\ndefines module qa.stringops.fuzzymatch\n\n  defines program\n    FuzzyMatchDemo()\n      stdout <- Stdout()\n\n      // === FUZZY MATCH COST (<~>) ===\n\n      // Identical strings — cost 0\n      greeting <- \"hello\"\n      sameGreeting <- \"hello\"\n      dist0 <- greeting <~> sameGreeting\n      stdout.println(`hello <~> hello = ${dist0}`)\n\n      // One substitution — cost 2\n      original <- \"kitten\"\n      dist1 <- original <~> \"sitten\"\n      stdout.println(`kitten <~> sitten = ${dist1}`)\n\n      // Several edits — cost 6 (the classic edit count would be 3)\n      dist3 <- original <~> \"sitting\"\n      stdout.println(`kitten <~> sitting = ${dist3}`)\n\n      // Too few shared characters — Integer maximum (no viable match)\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      // A transposition costs 4, so the threshold is in cost units\n      typoThreshold <- 4\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      // The lowest cost is the closest match\n      if d1 < d2 and d1 < d3\n        stdout.println(`Closest to colr: ${target1}`)\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 a weighted Levenshtein-style match cost as an Integer (case change 1, other edits 2, Integer max for no viable match), 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, which returns a weighted fuzzy-match cost. 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 :~: (in place) or + (a new list):\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 get the size of a List or Dict, or check whether it is empty?","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?","How do I get the number of elements in a List?","How do I check if a list or dict is empty in EK9?","How do I count the elements of a List with the length operator?","Is the size of a List 'length items' or a method call like items.length()?"],"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":"#!ek9\ndefines 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","length","size"],"typicalErrors":[{"error":"E50060","correct":"if emptyList is empty","incorrect":"if 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":"#!ek9\ndefines 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":["list first last","list first item","list last item"],"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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 never overwrites a set value; it fills whatever is unset or missing, key by key:\n  target :~: source\nA key the target lacks is added, a value the target holds unset is filled, and a value the target holds set is KEPT - the first recorded value wins. Nested dicts are merged by the same rule.\n\nDICT PLUS (+)\nCreate a new merged dict:\n  combined <- dict1 + dict2\nWith +, where keys overlap the right-hand dict's value is used.\n\nCOPY (:=:)\nCopy one collection into another:\n  target :=: source\nReplaces the target contents entirely with the source's elements. The copy is shallow: the elements themselves are shared with the source, not duplicated.\n\nSee Q88 for List operations. See Q90 for Dict operations. See Q130 for mutating vs non-mutating operators.","ek9Example":"#!ek9\ndefines 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      //b was already set in d1, so it keeps 2; c was missing, so it is added\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 (never overwrites a set value - for a Dict it is closer to putIfAbsent() than putAll()), + for new combined collection, :=: to replace the target's contents with the source's elements (a shallow copy).","keywords":["append","collection","combine","concatenate","copy","dict","join","list","merge","task","union"],"primaryTopics":["merge lists","merge dicts","combine collections"],"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. Unlike putAll(), a key the target already holds set keeps its value - only missing keys and unset values are filled. 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":"#!ek9\ndefines 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":["reverse list","list reversal"],"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":"#!ek9\ndefines 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 ?\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":"#!ek9\ndefines 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":["json serialize","json conversion"],"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":"Parse with the instance method parse(): JSON().parse(text) returns a Result of (JSON, String) - the parsed JSON on success, an error message when the text is not valid JSON. Check isOk() before ok(). Access properties with .get(key) for objects and .get(index) for arrays. Iterate with .iterator().\n\nPARSING JSON STRINGS\n  parseResult <- JSON().parse(`{\"name\": \"Alice\", \"age\": 30}`)\n  if parseResult.isOk()\n    person <- parseResult.ok()\n    stdout.println($person)\n\nCalling ok() without the isOk() check is E08030. Note the JSON(String) CONSTRUCTOR does not parse: it makes a JSON string value holding the text (objectNature() is false).\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":"#!ek9\ndefines module qa.jsonparsed.parsing\n\n  defines program\n\n    JsonParsingDemo()\n      stdout <- Stdout()\n\n      // === PARSING JSON STRINGS ===\n      // parse() is an instance method that returns Result of (JSON, String)\n\n      objectResult <- JSON().parse(`{\"name\": \"Alice\", \"age\": 30}`)\n      if objectResult.isOk()\n        parsed <- objectResult.ok()\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      arrayResult <- JSON().parse(`[10, 20, 30]`)\n      if arrayResult.isOk()\n        arr <- arrayResult.ok()\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}`)\n\n      // === INVALID TEXT GIVES AN ERROR, NOT AN EXCEPTION ===\n\n      badResult <- JSON().parse(\"not json\")\n      stdout.println(`Invalid text is ok: ${badResult.isOk()}`)\n\n      // === THE CONSTRUCTOR DOES NOT PARSE ===\n      // JSON(String) makes a JSON string VALUE holding the text\n\n      wrapped <- JSON(`{\"name\": \"Alice\"}`)\n      stdout.println(`Constructor gives object: ${wrapped.objectNature()}`)","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().parse(text) returns a Result of (JSON, String) - no exceptions, the compiler makes you check isOk() - then .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 with JSON().parse(text), which returns a Result of (JSON, String), then sum its numeric properties:\n  objectResult <- JSON().parse(`{\"a\": 1, \"b\": null, \"c\": 22}`)\n  if objectResult.isOk()\n    total <- cat objectResult.ok() | collect as Integer\n    stdout.println($total)\nThe total is 23: null values are skipped (they become unset in the tri-state model). The JSON(String) constructor does NOT parse - it makes a JSON string value, and summing that gives an unset result.\n\nNULL HANDLING\nJSON null becomes an unset value in EK9:\n  nullParse <- JSON().parse(`{\"val\": null}`)\n  if nullParse.isOk()\n    result <- cat List(nullParse.ok()) | collect as Integer\n    stdout.println(`unset: ${not result?}`)\n\nSee Q188 for JSON basics. See Q59 for function pipelines. See Q122 for collect as.","ek9Example":"#!ek9\ndefines 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      // parse() returns Result of (JSON, String); the null property is skipped\n\n      objectResult <- JSON().parse(`{\"first\": 1, \"second\": null, \"third\": 22}`)\n      if objectResult.isOk()\n        jsonObject <- objectResult.ok()\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      nullParse <- JSON().parse(`{\"mainValue\": null}`)\n      if nullParse.isOk()\n        jsonWithNull <- List(nullParse.ok())\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":"objectResult <- JSON().parse(`{\"first\": 1, \"second\": null, \"third\": 22}`)","incorrect":"objectResult <- JSON.parse(`{\"first\": 1, \"second\": null, \"third\": 22}`)","explanation":"parse() is an INSTANCE method, not a static one: call it on JSON() - JSON().parse(text) - and it returns a Result of (JSON, String). Check isOk() before ok() (an unguarded ok() is E08030). The JSON(String) constructor does not parse; it makes a JSON string value. 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 and replace work on JSON OBJECTS. Text becomes an object through JSON().parse(text), which returns a Result of (JSON, String) - check isOk() before ok(). The JSON(String) constructor does not parse: it makes a JSON string value, and merging another value into it keeps the first - a set value is never overwritten, and a value of another kind is never forced in.\n\nMERGE OPERATOR (:~:)\nMerge properties from one JSON object into another:\n  base :~: extras\nMerge never overwrites a set value: existing properties in base are kept, new properties from extras are added, a property base holds as null is filled, and nested objects are merged by the same rule (nested arrays append). A JSON null counts as unset, so it is filled; a top-level array appends what is merged into it.\n\nREPLACE OPERATOR (:^:)\nReplace matching properties:\n  target :^: replacement\nProperties in target that also exist in replacement are overwritten; properties only in replacement are not added.\n\nSee Q188 for JSON basics. See Q96 for operator semantics. See Q189 for converting to JSON.","ek9Example":"#!ek9\ndefines module qa.jsondata.manipulate\n\n  defines function\n\n    <?-\n      Parses JSON text into an object; parse() returns Result of (JSON, String).\n    -?>\n    parsedObject()\n      -> jsonText as String\n      <- rtn as JSON: JSON().object()\n      parseResult <- JSON().parse(jsonText)\n      if parseResult.isOk()\n        rtn := parseResult.ok()\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      // new properties from extras are added, existing ones in base are kept\n\n      base <- parsedObject(`{\"name\": \"Alice\", \"role\": \"dev\"}`)\n      extras <- parsedObject(`{\"team\": \"backend\", \"level\": 5}`)\n      base :~: extras\n      stdout.println(`After merge: ${base}`)\n\n      // === REPLACE OPERATOR :^: ===\n      // age exists in both so it is overwritten; city is not added\n\n      target <- parsedObject(`{\"name\": \"Alice\", \"age\": 30}`)\n      replacement <- parsedObject(`{\"age\": 31, \"city\": \"London\"}`)\n      target :^: replacement\n      stdout.println(`After replace: ${target}`)\n\n      // === A VALUE MERGED INTO A VALUE ===\n      // a set value is never overwritten, so the first is kept\n\n      single <- JSON(\"first\")\n      single :~: JSON(\"second\")\n      stdout.println(`Value merge: ${single}`)","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":["json merge","modify json","combine json"],"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\nBoth forms describe a parsed JSON value. Build one from text with JSON().parse(text), which returns a Result of (JSON, String) - check isOk() before ok(). The JSON(String) constructor does not parse: it makes a JSON string value, and both $ and prettyPrint() then show the same quoted one-line text.\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":"#!ek9\ndefines module qa.jsondata.output\n\n  defines function\n\n    <?-\n      Parses JSON text into an object; parse() returns Result of (JSON, String).\n    -?>\n    parsedObject()\n      -> jsonText as String\n      <- rtn as JSON: JSON().object()\n      parseResult <- JSON().parse(jsonText)\n      if parseResult.isOk()\n        rtn := parseResult.ok()\n\n  defines program\n\n    JsonOutputDemo()\n      stdout <- Stdout()\n\n      jsonData <- parsedObject(`{\"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 <- parsedObject(`{\"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":["json pretty","pretty json"],"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?\n    Container() as pure\n      item :=? T()\n    Container() as pure\n      -> initialItem as T\n      item :=? initialItem\n    default operator ?\nT is the type parameter that gets replaced with a real type.\n\nCONSTRUCTOR WITH TYPE PARAMETER\nA generic type needs two constructors, a default one and one that accepts a T-typed argument (the second one above). The argument is also what lets the type parameter be inferred.\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":"#!ek9\ndefines 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_new_construct","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  defines function\n    describe() of type T constrain by Animal as open\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":"#!ek9\ndefines 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 (a generic type needs both a default and an inferred-type constructor, E06040):\n  defines class\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      default operator ?\n\nINSTANTIATION\nProvide both types explicitly with empty parentheses (this creates an unset Pair):\n  pair <- Pair() of (String, Integer)\nOr give the values and let EK9 infer the types:\n  pair <- Pair(\"hello\", 42)\nGiving values AND an 'of' clause together is error E06220: choose either empty parentheses with a type definition, or values with inferred types.\n\nGENERIC FUNCTIONS WITH MULTIPLE PARAMETERS\nFunctions can also use multiple type parameters:\n  defines function\n    mapper() of type (S, T) as abstract\n      -> source as S\n      <- target as T?\n\nBUILT-IN EXAMPLES\nDict, Result, and DictEntry all use multiple type parameters:\n  counts <- Dict() of (String, Integer)\n  outcome <- 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":"#!ek9\ndefines 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":"A constructor call that supplies values cannot also carry an of type clause: choose either empty parentheses with a type definition, Pair() of (String, Integer), or values with inferred types, Pair(\"hello\", 42). 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_new_construct","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":"#!ek9\ndefines 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 - incompatible genus (the generic is a function, not a class). 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, 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 <- 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":"#!ek9\ndefines 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 for :/info open\n      welcome() as GET for :/welcome\n        <- response as HTTPResponse: ...\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 Bytes: Bytes().withUtf8(\"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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(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_new_construct","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() for :/             GET (read)\n  operator += for :/           POST (create/add)\n  operator -= for :/{id}       DELETE (remove)\n  operator :~: for :/{id}      PATCH (merge/partial update)\n  operator :^: for :/{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":"#!ek9\ndefines module qa.web.crud\n\n  defines service\n\n    Items for :/items open\n\n      // GET /items — list all items\n      listAll() for :/\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`[\"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 += for :/\n        -> content as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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 -= for :/{id}\n        -> id as String\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(\"\")\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 Bytes: Bytes().withUtf8(`{\"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: 200 for success, 201 for created, 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":"#!ek9\ndefines module qa.web.httpresponse\n\n  defines service\n\n    Health for :/health open\n\n      check() as GET for :/status\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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":["HTTPResponse","service response"],"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_new_construct","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  - the method is declared byId() as GET for :/{id}\n  - its parameter is declared -> 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":"#!ek9\ndefines module qa.web.parameters\n\n  defines service\n\n    Products for :/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 Bytes: Bytes().withUtf8(`{\"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 += for :/\n        -> content as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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    ServingApp\n      register InMemoryRepo() as Repository\n      register ItemService()\n\nSPLIT SERVING FROM COMPONENT WIRING\nAn application that registers a service must be used by a program that creates HTTP, otherwise the service is registered and never reachable (E12020). When a program needs only the components, give it its own application:\n  defines application\n    MyApp\n      register InMemoryRepo() as Repository\n\nPROGRAM WITH APPLICATION\nLink a program to an application for injection:\n  defines program\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":"#!ek9\ndefines 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 for :/items open\n\n      listAll() for :/\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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    // Serving wiring: registers the service, so its program MUST create HTTP (E12020).\n    ServingApp\n      register InMemoryRepo() as Repository\n      register ItemService()\n\n    // Component wiring only: safe for a program that does not serve.\n    MyApp\n      register InMemoryRepo() as Repository\n\n  defines program\n\n    ServeItems() with application of ServingApp\n      props <- NetworkProperties(8080)\n      HTTP(props).serve()\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":["application wiring","register services","service wiring"],"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_add_application","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, holding the single line: 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":["parameterized tests","data-driven tests"],"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; a fixed 80% threshold is enforced.\n\nCOVERAGE FLAG\nRun tests with coverage:\n  ek9 -tC myTests.ek9\nOr: ek9 -t4 myTests.ek9 (detailed coverage report listing uncovered items)\n\nWHAT IS RECORDED\nCoverage records whether each line was executed. There is no flag that selects any other counting mode.\n\nTHRESHOLD\nThe coverage threshold is 80% and is fixed - there is no setting to change it. When coverage falls below it the tests themselves still pass, but the run reports E83001 and exits with code 12 (all tests passed but less than 80% coverage).\n\nOUTPUT FORMATS WITH COVERAGE\n  -t4  Detailed report that lists the uncovered items\n  -t5  Verbose coverage listing both covered and uncovered items\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":"#!ek9\ndefines 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 ===\n      // Run with: ek9 -tC myTests.ek9\n      // Each line is recorded as executed or not.\n\n      stdout.println(\"Coverage: each line is recorded as executed or not\")\n      stdout.println(\"Threshold: 80% (fixed, E83001 and exit code 12 when missed)\")\n      stdout.println(\"Formats: -t4 uncovered items, -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 a fixed 80% threshold (E83001, exit code 12 when missed) and an HTML dashboard.","keywords":["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":"#!ek9\ndefines 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?","How do I test output containing dynamic values like dates or GUIDs in EK9?","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 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  {{Character}}       matches a single character\n  {{Bits}}            matches a run of 0s and 1s\n  {{Date}}            matches ISO dates (2026-09-29)\n  {{Time}}            matches times (14:30:00)\n  {{DateTime}}        matches ISO datetimes with offset\n  {{Duration}}        matches ISO durations (P1DT2H)\n  {{Millisecond}}     matches millisecond values (250ms)\n  {{Money}}           matches amounts with currency (10.50#GBP)\n  {{Colour}}          matches hex colours (#FF0000)\n  {{Resolution}}      matches dpi/dpc values\n  {{Dimension}}       matches dimension values\n  {{RegEx}}           matches any non-empty text\n  {{GUID}}            matches UUID format\n  {{FileSystemPath}}  matches file paths\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\nMIXING FIXED AND DYNAMIC\nPlaceholders can appear anywhere in a line, for example \"User alice created at {{DateTime}} with ID {{GUID}}\".\nFixed text is matched exactly; each placeholder matches its type's pattern.\n\nSee Q204 for black-box testing. See Q205 for parameterized tests. See Q157 for running tests.","ek9Example":"#!ek9\ndefines 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","black-box","capture","closure","coverage","date","dynamic","expected","format","guid","match","non-deterministic","output","placeholder","template","test","timestamp"],"primaryTopics":["dynamic test output","test dates guids","timestamp placeholder","output placeholder","test placeholder"],"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    stdout.println(\"Should not reach here\")\n  catch\n    -> ex as Exception\n    stdout.println(\"Caught: \" + $ex)\n\nThe callee must THROW explicitly. Integer division by zero does not throw - it returns an unset Integer - and a failed 'require' is a Panic that no catch block sees. So guard and throw:\n  if b == 0\n    throw Exception(\"division by zero\")\n\nASSERTTHROWS\nVerify that an expression throws an exception of a given type:\n  assertThrows(Exception, riskyFunction())\nThe test passes only if an exception is thrown.\n\nASSERTDOESNOTTHROW\nVerify that an expression completes without throwing:\n  assertDoesNotThrow(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":"#!ek9\ndefines 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      if b == 0\n        throw Exception(\"division by zero\")\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      // riskyDivide throws explicitly: dividing by zero alone would\n      // give an unset Integer, not an exception\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(Exception, riskyDivide(10, 0))\n      //   assertDoesNotThrow(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(Type, expr) / assertDoesNotThrow(expr) keywords, plus try/catch for detailed inspection. Integer division by zero gives an unset result rather than throwing, so throw explicitly.","keywords":["assertDoesNotThrow","assertThrows","catch","coverage","error","exception","test","throw","verify"],"primaryTopics":["assertThrows","assertDoesNotThrow"],"typicalErrors":[{"error":"E08091","correct":"require b?\n      if b == 0\n        throw Exception(\"division by zero\")\n      rtn: a / b","incorrect":"rtn: 0","explanation":"Parameters a and b are declared but a body that ignores them (a stub returning 0) never references them. Every parameter must be used. 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":"#!ek9\ndefines 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()\n        -> msg as String\n        delegate.log(\"[FILTERED] \" + msg)","incorrect":"log()\n        -> msg as String\n        delegate.log(\"[FILTERED] \" + msg)","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\n    stdout.println(\"Rendering generic shape\")\nThis method is the dispatch entry point.\n\nSPECIFIC OVERLOADS\nProvide overloaded methods for specific subtypes, beside the dispatcher:\n  render() as dispatcher\n    -> shape as Shape\n    stdout.println(\"Rendering generic shape\")\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":"#!ek9\ndefines 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_generate_dispatcher","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 and expose only the methods that make sense:\n  TaskQueue\n    items as List of String: List() of String\n    default TaskQueue()\n    addTask()\n      -> task as String\n      items += task\n    pendingCount() as pure\n      <- rtn as Integer: length items\n    hasWork() as pure\n      <- rtn as Boolean: length items > 0\n    default operator ?\n\nMULTI-CONCERN COMPOSITION\nComposition shines when wrapping multiple concerns. ValidatedConfig wraps a Dict and adds validation (get() with a default, set() with validation, has() for existence):\n  ValidatedConfig\n    entries as Dict of (String, String): Dict() of (String, String)\n    default ValidatedConfig()\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    has() as pure\n      -> key as String\n      <- rtn as Boolean: entries contains key\n    default operator ?\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 config.get(key), config.set(key, value) and config.has(key), not 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 Q763 for inheritance depth limits that encourage composition.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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 <- Inventory()","incorrect":"lock <- MutexLock(List() of String)   //local - rejected\n      inventory <- Inventory()","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  defines function\n    Formatter() as abstract\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":"#!ek9\ndefines 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 clean tainted input?","How do I handle user input safely in EK9?"],"answer":"The 'sanitized' modifier on a parameter makes the RECEIVING function clean that parameter at run time, as it is entered. It is not compile-time taint tracking: the compiler only checks where the modifier may be written and one aliasing rule (E07930).\n\nSANITIZED PARAMETER\nMark a parameter as sanitized:\n  processInput() as pure\n    -> input as sanitized String\n    <- result as String: input\nOn entry the function scans the value for threats (SQL injection, XSS, command injection, path traversal and others). A safe value becomes the function's own clean copy; a value carrying a threat arrives UNSET, so it prints nothing and a set-check on it fails.\n\nNO LOCAL ALIAS (E07930)\nIn any function, pure or not, you cannot alias the parameter into a new local ('local <- input'). Make an independent copy instead:\n  localCopy <- String(input)\nThis creates independent data. Using the parameter directly in an expression, or assigning it to the return variable, is also allowed.\n\nCOPYING INTO AN EXISTING VARIABLE\nIn any function, :=: or :~: copy into an initialized variable:\n  safeCopy <- String(sql)\n  result: \"\"\n  result :=: safeCopy\n\nDIRECT USE IN EXPRESSIONS\nUsing sanitized parameters in expressions 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. See Q661 for sanitized parameter restrictions. See Q662 for safe copy patterns. See Q663 for override matching.","ek9Example":"#!ek9\ndefines 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' on a parameter makes the receiving function clean it at run time (a threat arrives unset); the compile-time rules only cover where the modifier is allowed and the no-local-alias rule E07930.","keywords":["copy","injection","input","protect","runtime","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 when input is received?","How does EK9 prevent injection attacks?","What does the EK9 compiler check about sanitized parameters?"],"answer":"EK9 detects common injection threats at RUN time, at the point of use. A function that takes a 'sanitized' parameter scans the incoming value as it is entered. There is no compile-time taint tracking or data-flow analysis.\n\nWHAT HAPPENS ON ENTRY\nThe receiving function runs the input sanitizer on each 'sanitized' parameter before its body starts. It looks for SQL injection, XSS, command injection and path traversal, among other threats. A safe value becomes the function's own clean copy. A value carrying a threat is reported as a warning on stderr (naming the threat) and the parameter arrives UNSET.\n\nA THREAT ARRIVES UNSET\nAn unset String is nothing, so it prints nothing and a set-check on anything built from it is false:\n  outcome <- processUserQuery(request)\n  if outcome?\n    stdout.println(outcome)\n  else\n    stdout.println(\"Threat blocked: \" + request)\n\nEACH RECEIVER CLEANS ITS OWN PARAMETER\nWhen one function passes a sanitized parameter on to another, the second function cleans its own parameter again. There is no tainted flag travelling through the call chain.\n\nWHAT THE COMPILER CHECKS\nOnly the placement rules and one aliasing rule: 'sanitized' is for incoming String parameters (E07910, E07920) and a sanitized parameter may not be aliased into a local (E07930).\n\nSee Q215 for sanitized parameters. See Q218 for security best practices. See Q268 for OWASP vulnerability prevention. See Q272 for defense in depth.","ek9Example":"#!ek9\ndefines module qa.security.threats\n\n  defines function\n\n    // A sanitized parameter is scanned by the receiving function as it is entered\n    processUserQuery() as pure\n      -> userInput as sanitized String\n      <- result as String?\n\n      // An independent copy, never an alias of the parameter (E07930)\n      safeCopy <- String(userInput)\n      result: \"Safe query: \" + safeCopy\n\n    // The helper receives its own cleaned copy again: each receiver cleans its own parameter\n    handleRequest() as pure\n      -> requestData as sanitized String\n      <- result as String: processUserQuery(requestData)\n\n  defines program\n\n    ThreatDetectionDemo()\n      stdout <- Stdout()\n\n      // === RUN-TIME DETECTION AT THE POINT OF USE ===\n\n      requests <- [\"user-provided data\", \"x' OR '1'='1\", \"<script>alert(1)</script>\", \"../../etc/passwd\"]\n\n      for request in requests\n        outcome <- handleRequest(request)\n        if outcome?\n          stdout.println(outcome)\n        else\n          stdout.println(\"Threat blocked: \" + request)","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: the receiving function sanitizes a 'sanitized' parameter at run time and a detected SQL, XSS, command or path-traversal threat arrives unset; the compiler checks only placement and aliasing.","keywords":["command","detection","injection","migrate","protect","report","safe","security","sql","threat","xss"],"primaryTopics":["threat detection","security threats"],"typicalErrors":[{"error":"E07930","correct":"safeCopy <- String(userInput)","incorrect":"safeCopy <- userInput","explanation":"Assigning a sanitized parameter straight into a new local makes the local an alias of the parameter, in any function, pure or not, so the compiler reports E07930. Use the copy constructor, String(userInput), for an independent copy. See ek9 -h E07930 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":["Does a pure function need a copy constructor for a sanitized parameter?","How do purity and sanitization work together in EK9?","What is the copy constructor pattern for sanitized parameters?"],"answer":"Purity does not change how a sanitized parameter is used. The receiving function cleans the parameter on entry, pure or not, and the same rule applies in both: a sanitized parameter cannot be aliased into a new local (E07930). What purity adds is a ban on mutation operators such as :=: and :~: (E08120).\n\nPURE FUNCTION\nUse the parameter directly, or copy it:\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. 'localCopy <- input' would be E07930, exactly as it is in a function that is not pure. Assigning the parameter to the return variable, or using it in an expression, is allowed.\n\nWHY THEY INTERACT\nPurity prevents mutation and side effects. The no-alias rule prevents two names sharing the receiver's cleaned copy. Neither depends on the other.\n\nNON-PURE FUNCTION\nA function that is not pure may also copy into an existing variable:\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. See Q662 for safe copy patterns. See Q666 for injection pure context.","ek9Example":"#!ek9\ndefines module qa.security.pure\n\n  defines function\n\n    // Pure function: copy the parameter rather than alias it (E07930 applies in every function)\n    pureWithSanitized() as pure\n      -> input as sanitized String\n      <- result as String?\n\n      // An independent copy; 'cleanInput <- input' would be E07930\n      cleanInput <- String(input)\n      result: \"Clean: \" + cleanInput\n\n    // Non-pure may also copy into an existing variable\n    nonPureWithSanitized()\n      -> input as sanitized String\n      <- result as String?\n\n      // Option 1: Copy constructor (works in every function)\n      safeCopy <- String(input)\n\n      // Option 2: :=: on an initialized variable (not allowed in a pure function)\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(\"A sanitized parameter is never aliased into a local\")","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: the no-local-alias rule (E07930) for a sanitized parameter holds in every function; purity separately bans the mutation operators.","keywords":["constructor","context","copy","defensive","function","immutable","protect","pure","purity","safe","sanitize","sanitized","security","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E07930","correct":"cleanInput <- String(input)","incorrect":"cleanInput <- input","explanation":"Assigning a sanitized parameter straight into a new local makes the local an alias of the parameter. That is E07930 in every function, pure or not. Use the copy constructor String(input) for an independent copy. See ek9 -h E07930 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 function that declares it cleans it on entry, on every route.\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 (a MutexLock held in a local variable is E08256). The wrapping class looks like this:\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":"#!ek9\ndefines 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":["security practices","security best","secure coding"],"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)\nHue() creates unset. Hue(\"Red\") creates from string (unset if invalid). Hue(existing) copies.\n\nITERATOR\n'for colour in Hue' iterates all members. 'cat Hue' 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":"#!ek9\ndefines module qa.enums.batteries\n\n  defines type\n\n    Hue\n      Red\n      Green\n      Blue\n\n  defines program\n\n    EnumBatteriesDemo()\n      stdout <- Stdout()\n\n      red <- Hue.Red\n      blue <- Hue.Blue\n\n      // === COMPARISON OPERATORS (enum vs enum) ===\n\n      stdout.println(`Equal: ${red == Hue.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 <= Hue.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 <- Hue(\"Green\")\n      stdout.println(`From string: ${fromString}`)\n\n      invalid <- Hue(\"Purple\")\n      stdout.println(`Invalid isSet: ${invalid?}`)\n\n      unsetColour <- Hue()\n      stdout.println(`Unset isSet: ${unsetColour?}`)\n\n      copied <- Hue(red)\n      stdout.println(`Copied: ${copied}`)\n\n      // === ITERATION ===\n\n      for colour in Hue\n        stdout.println(`Iterate: ${colour}`)\n\n      colours <- cat Hue | collect as List of Hue\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":["enumeration free","enum batteries","enum automatic"],"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":"#!ek9\ndefines 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":["enum navigation","enum next previous","enum increment"],"typicalErrors":[{"error":"E50060","correct":"light++\n      stdout.println(`After ++: ${light}`)","incorrect":"light.next()\n      stdout.println(`After ++: ${light}`)","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\nDECLARATION GUARD PATTERN\nUse declaration guards 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 declaration guard.\n\nSee Q219 for all auto-generated operators. See Q222 for unset propagation. See Q99 for basic enumeration.","ek9Example":"#!ek9\ndefines 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 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. Declaration guards 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":"#!ek9\ndefines 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":["unset enum","enum unset state","enum tri-state"],"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 declaration guards 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":"#!ek9\ndefines 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 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":["enum subset","constrained enum","restricted enum"],"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  seasonCount <- length allSeasons\nseasonCount is the number 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":"#!ek9\ndefines 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":["enum stream pipeline","enum cat stream","enumeration stream"],"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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":["enum safety","enum bugs","exhaustive enum"],"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":"#!ek9\ndefines 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\n      register UpperFormatter() as Formatter","incorrect":"register UpperFormatter() as Formatter","explanation":"If the register statement for Logger is removed, the injection field 'logger as Logger!' in SimpleMessageService has no matching registration and the compiler rejects 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?","Register dependencies in the correct order so a service can inject them.","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":"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":"#!ek9\ndefines 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":["DI","after","application","before","chain","compile-time","dependency","explicit","initialization","inject","order","ordering","register","sequence"],"primaryTopics":["registration ordering","DI ordering"],"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":"#!ek9\ndefines 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":["circular dependencies","E08190"],"typicalErrors":[{"error":"E08190","correct":"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!","incorrect":"SimpleUserService is UserService\n      formatter as SharedFormatter!\n      orderService as OrderService!\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      userService as UserService!","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 with E08190 (and only E08190: no order of registrations could satisfy a cycle, so its members are not also reported as used before registration, E08200). The correct pattern extracts shared logic into a separate component. See ek9 -h E08190 for details."},{"error":"E08150","correct":"SimpleUserService is UserService\n      formatter as SharedFormatter!","incorrect":"SimpleUserService is UserService\n      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":"#!ek9\ndefines 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  defines program\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  defines program\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":"#!ek9\ndefines 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":["program application link","application connection"],"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!\n      stdout.println(greeter.greet(\"Alice\"))","incorrect":"greeter as FriendlyGreeter!\n      stdout.println(greeter.greet(\"Alice\"))","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":["dynamic class injection","dynamic class dependency injection","injected dynamic class"],"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":"    DynamicApp\n      register ConsoleLogger() as Logger","incorrect":"    DynamicApp","explanation":"An application with no registration has an empty body: removing the only register line leaves nothing to provide, and at least one implementation must 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":"#!ek9\ndefines 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":["component lifecycle","prepare decommission","injected singleton"],"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":"#!ek9\ndefines 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 String: List() of String\n    iterator() as pure\n      <- rtn as Iterator of String: 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  defines class\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    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\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":"#!ek9\ndefines 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?","When should I use a stream pipeline instead of a loop in EK9?","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":"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\nLOOP TO STREAM\n  Filter and collect   LOOP: for item in items / if condition / results += item\n                       STREAM: cat items | filter by condition | collect as List of T\n  Transform all items  LOOP: for item in items / transformed += convert(item)\n                       STREAM: cat items | map with convert | collect as List of T\n  Find first N         STREAM: cat items | filter by condition | head N | collect as List of T\nThe head operation replaces the break a loop would need (EK9 has no break).\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":"#!ek9\ndefines 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","refactor","replace","side","stream","transform","versus","vs","when","while"],"primaryTopics":["stream vs loop decision","stream pipeline or loop","loop or stream","stream vs loop","when to use stream pipeline"],"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). A type writes its sameness ONCE - <=> (ordered) or == (equatable only) - plus a matching #?; ==, <>, <, >, <=, >= with its own type are always generated from it (writing them is E07181).\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":"#!ek9\ndefines 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      //The sameness: ==, <>, <, <=, >, >= are generated from it\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?","incorrect":"<- rtn as String: 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\nSAMENESS: WRITE <=> ONCE\nA type declares its sameness once: <=> for an ordered type (or == for a type that can only be tested for equality). Every other comparison with the type itself is GENERATED by the compiler: from <=> come ==, <>, <, >, <=, >=; from == comes <>. Writing any of them by hand is E07181, so they can never disagree. A hand-written <=> needs a hand-written #? hashing the same fields (E07184).\n\nEQUALITY AND INEQUALITY\nThe == operator tests equality and returns Boolean. The <> operator tests inequality and returns Boolean.\n\nORDERING\nThe <, >, <=, >= operators test ordering and return Boolean.\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 <=>, the compiler derives ==, <>, <, >, <=, >= from it. Another ordering (e.g. sort by weight) is a comparator function given to sort, never a second <=>.\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  reference <- \"hello\"\n  distance <- reference <~> \"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":"#!ek9\ndefines 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      //The sameness: ==, <>, <, >, <=, >= are all generated from <=>\n      operator <=> as pure\n        -> other as Temperature\n        <- rtn as Integer: celsius <=> other.celsius\n\n      //A hand-written <=> needs a hand-written #? hashing the same field\n      operator #? as pure\n        <- rtn as Integer: #?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?","incorrect":"<- rtn as Integer: celsius?","explanation":"The ? operator must return Boolean (true or false). Returning Integer instead triggers E07520. (The same rule holds for ==, <>, <, >, <=, >= - but with the type itself those are generated from <=>, never written.) 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 and take 0 arguments. abs returns the same type as its operand; sqrt returns a Float (so sqrt on an Integer widens to Float: sqrt 16 gives 4.0).\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): the result ALWAYS has the same sign as the divisor (mathematical modulus).\n  - rem (remainder): the 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":"#!ek9\ndefines 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\nscore += 10 has the same arithmetic effect as score: score + 10, but += mutates the existing object (every alias of it sees the change) while score: score + 10 binds score to a new object and leaves any aliases unchanged.\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":"#!ek9\ndefines 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 value (the JSON type, not a String) holding the JSON representation. Declaring it with a String return is E07580.\n  operator $$ as pure\n    <- rtn as JSON: ...\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 items is empty\n    stdout.println(\"Nothing here\")\n  size <- length items\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":"#!ek9\ndefines module qa.operators.conversion\n\n  defines class\n\n    Amount\n      amount <- Float()\n      currency <- String()\n\n      Amount() 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      //The sameness (== and <> are generated from it): amount, then currency on a tie,\n      //so it compares exactly what #? hashes\n      operator <=> as pure\n        -> other as Amount\n        <- rtn as Integer: amount <=> other.amount\n        if rtn == 0\n          rtn: currency <=> other.currency\n\n  defines program\n\n    ConversionDemo()\n      stdout <- Stdout()\n\n      price <- Amount(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?","What are the coalescing operators in EK9?","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 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\nCOALESCE (ELVIS) (?:)\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\nCOMPARISON COALESCING OPERATORS\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":"#!ek9\ndefines 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      // === COMPARISON COALESCING OPERATORS ===\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 comparison coalescing operators. Swift: ?? nil coalescing (same operator as EK9!), no equivalent of ?: isSet check or <? >? comparison coalescing operators. JavaScript: ?? null coalescing (ES2020), || logical or fallback. EK9: ?? (memory check), ?: (isSet check), <? <=? >? >=? (comparison coalescing operators), :=? (assign-if-unset).","keywords":["absent","assignment","coalesce","coalescing","comparator","conditional","default","elvis","expression","fallback","greater","isset","lesser","maximum","migrate","minimum","null","operator","safe","swift","unset"],"primaryTopics":["coalescing","null coalescing","default value operator","coalescing operators","?? operator","<? >? operators"],"typicalErrors":[{"error":"E50030","correct":"displayName <- name ?: \"Anonymous\"","incorrect":"displayName <- name ?: 42","explanation":"The result of a coalescing operator is one of its operands, so the operands must share a type. A String and an Integer share only Any, which is rejected at the operator (E50030). See ek9 -h E50030 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  both <- true and false\n  either <- true or false\n  exclusive <- 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 available on Bits and on Byte. They shift bits left or right. Both are pure, take 1 Integer argument, and return a new value of the same type. Bits GROWS as needed on a left shift; Byte is fixed at eight bits, so bits shifted out are lost and the value never widens.\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. Bits have no arithmetic: + joins (concatenates) two Bits rather than adding them (0b1100 + 0b0011 gives 11000011). Beyond that they support: and, or, xor, ~ (NOT), << (shift left), >> (shift right), length, empty, comparison (==, <>, <, <=, >, >=, <=>), $ and $$.\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":"#!ek9\ndefines 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 and Byte.","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: a sameness (<=> or ==) with its #?, $ (string), ? (isSet).\n\nCOMPARISON - the sameness, written ONCE (pure, 1 arg)\n  operator <=> as pure -> other as T <- rtn as Integer   (ordered type)\n  operator == as pure -> other as T <- rtn as Boolean    (equatable-only type, instead of <=>)\nThe compiler generates the rest: ==, <>, <, <=, >, >= from <=>; <> from ==. Writing them by hand is E07181. A hand-written sameness needs a hand-written #? hashing the same fields (E07184).\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":"#!ek9\ndefines 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: the sameness, written ONCE (pure, 1 arg, returns Integer) ===\n      // ==, <>, <, <=, >, >= with another Temperature are generated from it\n\n      operator <=> as pure\n        -> other as Temperature\n        <- rtn as Integer: 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 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 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?","incorrect":"<- rtn as Integer: 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_operator","intent":"operators","description":"Oracle can add each operator to an existing class with the correct signature and purity - one call per operator, or body 'default' for the defaultable ones."}}
{"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 many 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 many 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 the program terminates with a Panic that names the source location, for example 'Panic: Requirement failed: `v > 10` at ./myfile.ek9:8:7'. A require breach is a contract failure, not an exception: try/catch cannot catch it. 'require' is also the tool for postconditions and invariants in production code. 'assert' is a test construct: it is allowed in @Test and dev source files, and is rejected in ordinary production source (E81015).\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 include debugging information: the JSR-45 source map and the line and local variable tables that let a debugger step through your .ek9 source and show its variables. Panic messages already carry the source location without -cg; -cg is what 'ek9 -d' and 'ek9 -dap' need.\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\nEK9 ships a debugger. 'ek9 -dap' runs the compiler as a Debug Adapter Protocol server, so an editor such as VS Code can set breakpoints in .ek9 source, show the EK9 stack and local variables, and step over, into and out. 'ek9 -d <port>' runs a program built with debugging information (-cg) in debug mode on a port. Use diagnostic output and the testing framework alongside it.\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":"#!ek9\ndefines module qa.debugging.program\n\n  defines function\n\n    validateScore() as pure\n      -> score as Integer\n      <- rtn as Boolean?\n\n      require score?\n      require score >= 0\n      require score <= 100\n      rtn: true\n\n    // A plain predicate, so a bad value can be REJECTED rather than fataly breaching a require\n    inRange() as pure\n      -> score as Integer\n      <- rtn as Boolean?\n\n      lowestValid <- 0\n      highestValid <- 100\n      rtn: score? and score >= lowestValid and score <= highestValid\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      // require is a CONTRACT, not an exception: a breach is a Panic that terminates the\n      // program, and try/catch CANNOT catch it. So test the value BEFORE constructing.\n      candidate <- -1\n      if inRange(candidate)\n        stdout.println(`Constructed: ${GameScore(candidate)}`)\n      else\n        stdout.println(`Rejected before construction: ${candidate}`)\n\n      // try/catch is for thrown Exceptions, which ARE catchable\n      try\n        throw Exception(\"recoverable problem\")\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(`Parsed value: ${badParsed}`)\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 always active (assert only in test and dev source), stdout.println with $ operator, -cg debugging information, built-in black-box testing, LSP real-time errors, a DAP debugger (-dap, -d).","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":"E08090","correct":"valid <- validateScore(85)\n      stdout.println(`Score 85 valid: ${valid}`)","incorrect":"unused <- validateScore(85)\n      stdout.println(`Score 85 valid: true`)","explanation":"If a declared variable is never referenced after assignment, the compiler rejects it. Every variable must be used. See ek9 -h E08090 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\nThe compiler first names the phase and the file, then reports each error on one line:\n  EK9     : FULL_RESOLUTION: Errors in ./myfile.ek9\n  EK9     : Error   : EXXXXX: 'token' on line L position C: message\nFor example, using a variable that was never declared:\n  EK9     : Error   : E50001: 'unknownVar' on line 7 position 31: 'unknownVar': not resolved\n             See: ek9 -h E50001\nThe token, line number and position tell you exactly where the error is, and the EXXXXX code uniquely identifies the error type (E50001 is 'not resolved'; E50030 is a different error, incompatible types).\n\nVERBOSITY LEVELS\nEK9 supports four error formats, controlled by -E0 through -E3. (-E4, -E5 and -E6 are not error formats: they print help and bootstrap summaries.)\n\n-E0 (Minimal, the default): Single-line error messages with the error code, the token and its line and position. 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 is not an error format. 'ek9 -h -E4' prints a concise paradigm summary of EK9's key design decisions, and '-E5' adds the full AI bootstrap reference.\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":"#!ek9\ndefines 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: four error formats (-E0 to -E3) 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 E50001\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. A few examples from each (the meaning shown is the first line of 'ek9 -h <code>'):\n\nE01xxx - Names, References and Excluded Keywords\n  E01010: A reference is missing its module qualifier ('::')\n  E01020: The EK9 core module names (org.ek9.lang, org.ek9.math) are reserved\n  E01030: A name matches an existing symbol of a different kind in scope\n  E01072: 'return' does not exist in EK9\n\nE02xxx - Duplicate Definitions\n  E02010: A property with this name already exists in the type hierarchy\n  E02030: Two or more methods have the same signature\n\nE04xxx - Type Constraints and Conversion\n  E04010: A constraint applied to a type that cannot be constrained\n  E04020: A string interpolation value cannot be converted to a String\n\nE05xxx - Type Hierarchy\n  E05020: Circular type hierarchy detected\n  E05030: Type is not open for extension (closed by default)\n  E05120: Method or operator must use the override keyword (it overrides a parent's)\n\nE06xxx - Parameters and Arguments\n  E06260: A 'defines package' property has a value of the wrong kind\n  E06280: Too many arguments\n  E06290: Too few arguments\n  E06330: Incompatible type arguments for a generic type\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: Must return Boolean\n  E07550: Must return Integer\n  E07570: Must return String\n  E07580: Must return JSON\n  E07620: The type has no such operator\n\nE08xxx - Flow Analysis and DI\n  E08020: Variable used before it is initialised\n  E08030: Optional, Result or Iterator accessed without checking it first\n  E08050: Return variable not initialised on all paths\n  E08055: Return initialised before a require (declare 'as Type?', assign after the require)\n  E08190: Circular dependency injection detected\n\nE11xxx - Code Quality\n  E11010: Cyclomatic complexity exceeds the threshold\n  E11031: Non-descriptive variable name\n  E11032: Variable name shadows a built-in operator keyword\n  E11040: A component has too many injected fields\n\nE50xxx - Resolution and Type Compatibility\n  E50001: Variable or identifier not resolved\n  E50030: Incompatible types (one type used where a different one is expected)\n  E50060: No method matches that name and those parameter types\n\nCOMMON FIRST ERRORS\nErrors new EK9 developers often meet first:\n- E50001: check the spelling and ensure references 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 the number of arguments\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":"#!ek9\ndefines 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      //The sameness is the account's identity (its name); == and <> are generated from it\n      operator <=> as pure\n        -> other as Account\n        <- rtn as Integer: name <=> other.name\n\n      operator #? as pure\n        <- rtn as Integer: #? name\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","E50001","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 phases 0-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":"#!ek9\ndefines 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 'sensorLabel' is declared but never used in any expression, the compiler rejects it. Every declared variable must be referenced. See ek9 -h E08090 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 promotes an Integer to a Float automatically (the #^ operator is applied for you, at assignment and at call arguments), but every other conversion must be explicit.\n\nCOMMON FIXES\n\nInteger to Float: #^ promote, or let the declared type promote it:\n  intVal <- 42\n  floatVal <- #^ intVal\n  widened 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: a String where a Float is expected is rejected, while an Integer is promoted for you:\n  processFloat() as pure\n    -> floatInput as Float\n    <- rtn as String: `Processed: ${floatInput}`\n\n  rejected <- processFloat(\"42\")   // ERROR E06270\n\nThese are fine, the Integer 42 is promoted to Float:\n  processFloat() as pure\n    -> floatInput as Float\n    <- rtn as String: `Processed: ${floatInput}`\n\n  fromInteger <- processFloat(42)\n  fromLiteral <- processFloat(42.0)\n  explicit <- processFloat(#^ 42)\n\nOperator Return Types (enforced):\n  - == must return Boolean (E07520)\n  - $ must return String (E07570)\n  - #? and <=> must return Integer (E07550)\n  - a mutating operator such as += must NOT return a value (E07430)\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":"#!ek9\ndefines 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 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(`Parsed: ${bad}`)\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: Integer promotes to Float automatically, other conversions 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 with no value at all ('as Type?') and used it before assigning one:\n  count as Integer?      declared, no value yet\n  total <- count + 1     // ERROR E08020: count might be used before being initialised\n  stdout.println($total)\nFix: give it a value before the use (or declare it with one):\n  count as Integer?\n  count: 0\n  total <- count + 1     now safe\nNote the difference: count <- Integer() IS initialised. It holds a present-but-unset Integer, can be used freely, and count + 1 is then simply unset.\n\nE08030 - Not Checked Before Access:\nYou called an accessor on an Optional, Result or Iterator without checking it first:\n  lookup()\n    <- rtn as Optional of String: Optional(\"found\")\n\n  maybe <- lookup()\n  stdout.println(maybe.get())     // ERROR E08030: maybe has not been checked before access\nFix: check it, or use a guard:\n  if maybe?\n    stdout.println(maybe.get())   safe\nAn ordinary value such as Integer(someString) needs no check to be used; it may simply be unset.\n\nE08050 - Return Not Always Initialised:\nYour function declares its return variable with no value and some path never assigns it:\n  calculateArea()\n    -> shape as String\n    <- rtn as Float?\n    if shape == \"circle\"\n      rtn: 3.14\n    // ERROR: rtn not set when shape is not circle\nFix: assign it on ALL paths (a path that ends in a throw never returns, so it needs no value), or give it an initial value (<- rtn as Float: Float()).\n\nGUARDS\nThe preferred way to handle potentially unset values is with guards. 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\nASSIGN-IF-UNSET (:=?)\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 coalescing operator ?: (Elvis) provides a default when a value is unset:\n  safe <- maybeUnset ?: \"fallback\"\nThe ?? operator is NOT a default for an unset value: it only replaces an ABSENT one, so an unset value such as String() passes straight through and the result is still unset (see Q1059).\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":"#!ek9\ndefines 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: 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      // Assign-if-unset 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 (Elvis) ?: (?? would pass the unset value through)\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, guards, :=? assign-if-unset, 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 FORMATS (-E0 through -E3)\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\nThe default is -E0. Use -E3 when learning EK9 or when an error is confusing. (-E4, -E5 and -E6 are not error formats: they print the paradigm summary and the AI bootstrap references, for example 'ek9 -h -E4'.)\n\nCOMPILATION MODES\n  -c     Incremental compile (only recompile changed files)\n  -C     Full recompile (recompile everything from scratch)\n  -r name   Which program to run, when the EK9 file defines more than one\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    Compile with debugging information, so 'ek9 -d <port>' and 'ek9 -dap' can step through EK9 source\n  -cd    Combined development and debug mode\nA failed require reports its source location without -cg; the -cg flag is what a debugger needs.\n\nANALYSIS FLAGS\n  -di    DI/AOP analysis: outputs dependency injection wiring and aspect pointcut information\n  -Cp PHASE  Stop at the named phase (for example -Cp PRE_IR_CHECKS, not a number): compiles up to that phase 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  -t, -t1   Human-readable test output (the default)\n  -t0       Terse output, a minimal summary for scripting\n  -t2       JSON output, for tools and AI\n  -t3       JUnit XML output, for CI/CD integration\n  -t4       Detailed coverage report listing what is uncovered\n  -t5       Verbose coverage report listing what is covered and uncovered\n  -t6       Interactive HTML coverage report\nUse -t3 for CI/CD integration.\n\nCOVERAGE\n  -tC    Display coverage results after the tests have run\n  -tp    Add profiling (call counts and durations) to the test run; -t2p and -t6p give JSON and an HTML flame graph\nCoverage is reported with the tests, and the threshold is 80%: when all tests pass but coverage is lower, the exit code is 12.\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":"#!ek9\ndefines 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: four error formats, debugging information for a DAP debugger, phase-level control, built-in test formats, coverage and profiling, 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":["verbose compilation","debug mode"],"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 23-phase compilation pipeline (phases 0-22). 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-10)\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.\nPhase 10 - PLUGIN_RESOLUTION: Resolves external plugin points.\n\nMIDDLE PHASES (11-14)\nPhase 11 - SYMBOLIC_ANALYSIS: Bounded-model contract verification. Off by default and advisory only - it never adds errors to a normal build.\nPhase 12 - IR_GENERATION: Generates the intermediate representation (IR) from the validated AST. The IR is the common format for all backends.\nPhase 13 - IR_ANALYSIS: Analyses the IR, including retain/release balance for memory safety.\nPhase 14 - IR_OPTIMISATION: Applies IR-level optimisations (currently a stub, deferred by design until both backends are feature-complete).\n\nBACKEND PHASES (15-22)\nPhase 15 - CODE_GENERATION_PREPARATION: Prepares for target code generation.\nPhase 16 - CODE_GENERATION_CONSTANTS: Generates code for constant values.\nPhase 17 - CODE_GENERATION_APPLICATIONS: Generates code for application entry points.\nPhase 18 - CODE_GENERATION_AGGREGATES: Generates code for classes, records, traits, and other aggregates.\nPhase 19 - CODE_OPTIMISATION: Applies target-specific code optimisations.\nPhase 20 - PLUGIN_LINKAGE: Links external plugins into the compiled output.\nPhase 21 - APPLICATION_PACKAGING: Packages the compiled application.\nPhase 22 - 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 <PHASE>' flag stops compilation at the named phase. The flag takes the PHASE NAME, not a number - for example '-Cp PRE_IR_CHECKS', not '-Cp 9'. 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.\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":"#!ek9\ndefines 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      //It adds fields, so it declares its own '$' (E07238) - here, deliberately, the super's representation.\n      override operator $ as pure\n        <- rtn as String: $super\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      //It adds fields, so it declares its own '$' (E07238) - here, deliberately, the super's representation.\n      override operator $ as pure\n        <- rtn as String: $super\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 23-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\nOPERATORS AND ANY\nAny carries no usable operator except ? (isSet). A value held as Any can be asked x? but not x == y or $x: those are E07620 (operator not defined).\nYour own types do NOT inherit operators from Any either. Six operators form the canonical set every type is expected to provide for itself:\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\nA class provides them with 'default operator' or explicit definitions (sameness rules: E07181/E07184), and a type with fields must define ? (E07235). Until it does, a == b on it is E07620. Built-in types such as String define them already, which is why the program below can use ?, $ and #? on a String.\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; declare it: items as List of Any: [42, \"hello\"] (an undeclared [42, \"hello\"] shares no type but Any and is E50030)\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":"#!ek9\ndefines 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      // A heterogeneous list DECLARES its element type: elements that share no type but Any are E50030\n      items as List of Any: [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 as List of Any: [Date(), Duration()]\n      for mixedItem in mixedItems\n        stdout.println(describer.describe(mixedItem))\n\n      // String defines ?, $ and #? itself - Any does not supply them\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) that carries no usable operator except ?; each type provides ==, <=>, $, $$, #? itself (default operator or explicit), 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":"E50030","correct":"items as List of Any: [42, 3.14, \"hello\", true]","incorrect":"items <- [42, 3.14, \"hello\", true]","explanation":"Integer, Float, String and Boolean share no type but Any, and EK9 never invents a List of Any - an undeclared mix is E50030 at the literal. Declare the element type when a heterogeneous list is what you want."},{"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:\n1. Exact type match scores 100% - always called first\n2. Parent class match scores near 100% - called for subtype\n3. Trait match scores near 100% - called for implementor\n4. Promotion match scores 99.5% - called after type widening\n5. 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":"#!ek9\ndefines 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, so render is not overloaded - no ambiguity:\n      // only render(Shape, Circle) exists, circle matches Shape at 0.05 and small matches Circle at 0.05\n      renderer.render(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 COMPILE-TIME overload resolution using declared types, five distinct cost levels, ambiguity detection within 0.001 tolerance; runtime-type selection happens only for methods marked as dispatcher, which reuse the same cost model.","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 E06140 METHOD_AMBIGUOUS. See ek9 -h E06140 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 or assign the result of a Void function to a variable (E10010).\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: v <- Void() is rejected with E10010\n- Cannot assign result: result <- voidFunction() is rejected with E10010\n- Void is only ever the implicit return type of a function or method. Do not declare variables, parameters or type arguments of Void: the compiler does not reject all of those yet, and they do not produce working programs.\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":"#!ek9\ndefines 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 assigned (E10010), 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\nA CONSTRAINT'S LITERAL MUST MATCH EXACTLY\nThe literal in a 'constrain as' is compared with the base type's operator exactly as written - it is never promoted. A Float constraint writes '>= 1.0', not '>= 1' (E08265), and a String constraint writes '<> \"x\"', not '<> 'x''. For a user-defined base you may instead add the operator that takes the literal's type. See Q1391.\n\nRANGE CONSTRAINTS WITH CONSTRAIN\nThe 'constrain' syntax restricts numeric values to a range:\n  defines type\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, as in 'describeAnimal() of type T constrain by Animal as open'.\nThis preserves IS-A relationships: T must be a subtype of the bound (here the user-defined class Animal; see Q195 for the complete compiling example). 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  defines function\n    processEmail()\n      -> addr as EmailAddress\n      <- rtn as Boolean: addr?\n      // Only accepts validated emails\n    processString()\n      -> text as String\n      <- rtn as Boolean: text?\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":"#!ek9\ndefines 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    //A Float constraint states its bound as a Float - 1.0, never 1 (E08265, see Q1391).\n    Ratio as Float constrain as\n      >= 1.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      // === ASSIGN-IF-UNSET 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":"ValidPerson as Person constrain as\n      matches /^[a-zA-Z]+ [a-zA-Z]+$/","incorrect":"ValidPerson as Person constrain as\n      > 0","explanation":"A constraint expression may only use operators that its base type provides. Person declares 'operator matches' but has no '>' operator, so the constraint '> 0' on ValidPerson cannot be resolved and the compiler reports E50060 on that line. Constrained types inherit operators from their base - a constraint that needs an operator the base does not have breaks. See ek9 -h E50060 for details."},{"error":"E08265","correct":"    Ratio as Float constrain as\n      >= 1.0","incorrect":"    Ratio as Float constrain as\n      >= 1","explanation":"A constraint's literal must match the operator's parameter type exactly - it is never promoted. Float's '>=' takes a Float, so write 1.0. See Q1391."}],"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 method 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":"#!ek9\ndefines 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  okState <- r.isOk()        // true\n  errorState <- 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":"#!ek9\ndefines 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 the record's sameness: '==' (and '<>') are generated from '<=>', never written or requested separately.\n\nKEY REQUIREMENTS\nA type used as a Dict key must implement:\n  operator == (equality) - used for Dict key matching at runtime; generated from the type's sameness\n  operator #? (hashcode) - used for Dict O(1) hashing at runtime; required, and hashes what the sameness compares\n  operator <=> (comparison) - the sameness of an ordered type (use '==' as the sameness for an equatable-only type); '==' is generated from it and it is used 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 ?\nThe 'default' keyword auto-generates these operators based on field declarations; '==' and '<>' are always generated from the '<=>' sameness, so writing 'default operator ==' beside it is an error (E07181).\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":"#!ek9\ndefines 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\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, where '<=>' is the sameness and '==' is generated from it.","keywords":["advanced","comparison","custom","dict","enumeration","hashcode","insertion","key","operator","order","record","requirement","type","type-system"],"primaryTopics":[],"typicalErrors":[{"error":"E07184","correct":"default operator <=>","incorrect":"//removed compare","explanation":"Removing 'default operator <=>' from PlayerName leaves the record with no sameness ('<=>' or '=='), so 'default operator #?' has nothing to agree with and fails with E07184. Without sameness there is no generated '==', and the type cannot function as a Dict key. Declare the sameness once ('<=>' for an ordered type, '==' for an equatable-only one) and let the compiler generate the rest. See ek9 -h E07184 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). Assign-if-unset :=? (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":"#!ek9\ndefines module qa.patterns.idiomatic\n\n  defines function\n\n    isNonEmpty() as pure\n      -> item as String\n      <- rtn as Boolean: length item > 0\n\n  defines program\n\n    IdiomaticEk9Demo()\n      stdout <- Stdout()\n\n      // === GUARD ===\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      // === ASSIGN-IF-UNSET ===\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":["idiomatic ek9","idiomatic code","ek9 idioms"],"typicalErrors":[{"error":"E50001","correct":"if msg <- greeting","incorrect":"if msg <- greetingXYZ","explanation":"A name that was never declared (here greetingXYZ) is not resolved, triggering E50001 — not resolved. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"stdout.println(`Welcome to ${lang}`)","incorrect":"stdout.println(`Welcome to ${langXYZ}`)","explanation":"A name that was never declared (here langXYZ) is not resolved, 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  defines 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: 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 capture binds the object that prefix referred to when the listener was created. Mutating operators on that object (prefix += \"X\") are seen by the listener, but reassigning the outer variable (prefix: \"NEW\") is not. For a snapshot, capture a copy: (prefixCopy: String(prefix)) is Listener as function.\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":"#!ek9\ndefines 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.subscribe(printListener)","incorrect":"emitter.subscribe(printListner)","explanation":"A misspelt name (printListner) is not resolved, 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  defines trait\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":"#!ek9\ndefines 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()\n        -> msg as String\n        <- rtn as String: \"[CONSOLE] \" + msg","incorrect":"log()\n        -> msg as String\n        <- rtn as String: \"[CONSOLE] \" + msg","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":"#!ek9\ndefines 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: 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":"#!ek9\ndefines 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":["fluent chaining","builder chaining","fluent interface"],"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: qb.select(\"name, age\")","incorrect":"qb: qbs.select(\"name, age\")","explanation":"Using a variable name that was never declared - 'qbs' instead of 'qb' - leaves it unresolved and triggers E50001 on that line. 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  defines 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":"#!ek9\ndefines 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":["cross-cutting concerns","logging middleware"],"typicalErrors":[{"error":"E05120","correct":"      override execute()\n        -> request as String\n        <- rtn as String?","incorrect":"      execute()\n        -> request as String\n        <- rtn as String?","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()\n\n      LoggingService()","incorrect":"      delegate as Service\n\n      LoggingService()","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 GUARDS\nBad: Assuming a value is always set and using it directly. Operators on unset values are defined: the result is unset, so nothing crashes and the bug shows up later as missing output or a missing value. For Optional and Result, unguarded access such as get() is a compile error (E08030), so that case cannot be written at all.\nGood: Always use guards 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 guards 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":"#!ek9\ndefines 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 ===\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: Assign-if-unset 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":["anti-patterns","bad practice"],"typicalErrors":[{"error":"E50001","correct":"stdout.println(order.summary())","incorrect":"stdout.println(orderr.summary())","explanation":"A misspelt name (orderr) is not resolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"stdout.println(product.describe())","incorrect":"stdout.println(prodct.describe())","explanation":"A misspelt name (prodct) is not resolved, 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 makes the receiving function clean its parameter at run time, as it is entered. Runtime detectors scan for SQL injection, XSS, command injection, path traversal, LDAP injection, header injection and expression injection, and a value carrying a threat arrives unset. There is no compile-time taint tracking: the compiler only checks where 'sanitized' may be written and rejects aliasing the parameter into a local (E07930).\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. Guards enforce safe value access before use.\n\nA05 SECURITY MISCONFIGURATION\nConstrained types enforce valid configuration at construction time. For untrusted input use Type().of(value): a value that breaks the constraint gives an unset object that a guard catches, so misconfiguration cannot silently propagate. A bare constructor call asserts the value is valid, so a violation is a Panic (or E08260 when the value is a compile-time constant).\n\nA08 DATA INTEGRITY FAILURES\nPure 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 parameters on URL and path parameters mean the receiving function scans each value on entry, so a user-provided URL or path carrying a recognised threat (path traversal, for example) arrives unset instead of being used.\n\nSee Q29 for tri-state model. See Q54 for pure functions. See Q74 for guards. 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":"#!ek9\ndefines 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      // === GUARDS (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, everything is mutable. Rust: ownership model prevents some categories. Go: no generics until recently, no purity. Kotlin: null safety helps but open by default, no sanitization. EK9: closed types, the sanitized keyword (run-time cleaning on entry), pure functions, tri-state model, no null, constrained types, guards prevent multiple OWASP categories by design.","keywords":["attack","cve","design","language","owasp","prevention","protect","safe","secure","security","vulnerability"],"primaryTopics":["security vulnerabilities","vulnerability prevention"],"typicalErrors":[{"error":"E50001","correct":"result <- processExternalInput(userInput)","incorrect":"result <- processExternalInpt(userInput)","explanation":"A misspelt function name cannot be resolved, so E50001 is reported on that line. Spell the declared function name exactly. 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, declaration guards for conditional execution, require statements for preconditions, and sanitized parameters that clean external input on entry.\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\nDECLARATION GUARDS\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 makes the RECEIVING function clean the parameter the moment it is entered, at run time, on every route into it:\n  -> userInput as sanitized String\nA detected threat (an injection, a script) arrives as an UNSET String and is logged. The compiler guarantees that cleaning step exists - the author cannot forget it - but it does not track the value afterwards.\n\nFOUR LAYERS TOGETHER\nThe complete validation flow uses all four:\n  1. sanitized cleans the input on entry (a threat becomes unset)\n  2. Constrained type validates the format\n  3. Declaration guard 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 declaration guards. See Q268 for OWASP vulnerability prevention. See Q272 for defense in depth.","ek9Example":"#!ek9\ndefines 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: String()\n\n      // Layer 1: 'sanitized' - this function cleans 'input' on entry, at run time;\n      //          a detected threat arrives as an unset String\n      // Layer 2: the constrained type validates the format\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      //.of() returns an UNSET EmailAddress when the value violates the constraint\n      badEmail <- EmailAddress().of(\"not-an-email\")\n      if not badEmail?\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      badPort <- Port().of(99999)\n      if not badPort?\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      //A rejected input leaves the result unset\n      invalidResult <- validateAndProcess(\"bad\")\n      if not invalidResult?\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":["input validation","validate input","validation strategy"],"typicalErrors":[{"error":"E08260","correct":"if port <- Port().of(8080)","incorrect":"if port <- Port(99999)","explanation":"A bare constructor asserts the value is valid. With a literal the compiler can prove violates the constraint (99999 > 65535) it is a compile error rather than a runtime panic. Use the fallible .of(value) factory for values you do not already trust. See ek9 -h E08260 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 as Version: 1.0.0-0\n    description as String = \"My application\"\n    deps <- {\n      \"com.example.utils\": \"2.1.0-0\"\n    }\nSemantic versioning is enforced: within the same MAJOR.MINOR the highest PATCH is selected, and a different MAJOR version fails the build, so an unexpected breaking upgrade cannot slip in.\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\nEvery dependency names an explicit version, so the resolved dependency set is repeatable and a surprise major upgrade fails the build rather than arriving silently. Naming each dependency explicitly is also the place to notice a similarly-named malicious package (typosquatting):\n  deps <- {\n    \"org.trusted.lib\": \"1.2.3-0\"\n  }\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":"#!ek9\ndefines 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.println(\"Planned security infrastructure:\")","incorrect":"stdoutXYZ.println(\"Planned security infrastructure:\")","explanation":"Misspelling the variable where it is used (stdoutXYZ) means it does not resolve, 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 declaration guards 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\nA function that takes a sanitized parameter cleans its own copy on entry, so a value carrying an injection threat arrives unset:\n  connectToService()\n    -> apiKey as sanitized String\n    <- result as String: apiKey\nThis is run-time threat cleaning by the receiving function. It does not stop a value being logged and it tracks nothing through the call chain.\n\nKEEPING SECRETS OUT OF LOGS\nFor a real credential use env.sensitiveGet(name), which returns a Sensitive. Its '$' always gives ***REDACTED*** and reveal() is gated by the Privileged trait. See Q701.\n\nWHAT NOT TO DO\nNever embed secrets in source code. Never log secret values (keep them in a Sensitive, whose '$' is redacted). 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 declaration guards. See Q268 for OWASP vulnerability prevention. See Q272 for defense in depth.","ek9Example":"#!ek9\ndefines 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        // connectToService cleans its own sanitized parameter on entry\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":"env <- EnvVar()","explanation":"The type is EnvVars. A misspelt type name cannot be resolved, so E50001 is reported on that line. See ek9 -h E50001 for details."},{"error":"E50001","correct":"result <- connectToService(apiKey)","incorrect":"result <- connectToServic(apiKey)","explanation":"A misspelt function name cannot be resolved, so E50001 is reported on that line. Spell the declared function name exactly. 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 the parameter that receives untrusted input at the system boundary:\n  processRequest()\n    -> input as sanitized String\n    <- result as String: input\nThe RECEIVING function cleans the parameter the moment it is entered, at run time, on every route into it: a detected threat (an injection, a script) arrives as an UNSET String and is logged. The compiler guarantees the cleaning step exists - the author cannot forget it - but it does not track the value afterwards.\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 declaration guard 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    -> content as String\n    <- result as String: \"Processed: \" + content\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 neutralises injection and script payloads on entry. 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 declaration guards. 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":"#!ek9\ndefines 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: String()\n\n      if content?\n        result: \"Processed: \" + content\n\n    // Complete defense-in-depth pipeline\n    processRequest()\n      // Layer 1: sanitized entry point - cleaned on entry, at run time\n      -> input as sanitized String\n      <- result as String: String()\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      //A rejected request leaves the result unset\n      invalidResult <- processRequest(\"not-email\")\n      if not invalidResult?\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 — cleans input on entry\")\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; the compiler guarantees each layer is present, and the sanitizing and constraint checks run on every call.","keywords":["chain","combine","compile","defense","depth","layer","migrate","multi","pipeline","protect","safe","security","stack"],"primaryTopics":["security layers","layered security"],"typicalErrors":[{"error":"E08120","correct":"        result: \"Processed: \" + content","incorrect":"        result += \"Processed: \" + content","explanation":"The processing layer is pure, and a pure scope may not use mutation operators such as '+=' - not even on its return variable. Assign the newly computed value with ':' instead. See ek9 -h E08120 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 (E08120): +=, -=, *=, /=, :=:, :~: and :^:. These 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\nSANITIZED PARAMETERS IN PURE FUNCTIONS\nA sanitized parameter is cleaned by the receiving function on entry. It cannot be aliased into a local (E07930), in a pure function or any other, and a pure function cannot mutate it either. Copy it with the copy constructor, or use it directly:\n  secureProcess() as pure\n    -> input as sanitized String\n    <- result as String?\n    localCopy <- String(input)\n    result: localCopy\nPurity only adds the ban on mutation operators; the no-alias rule is not specific to pure functions.\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() as pure\n    -> handler as Consumer of String\n    handler(\"data\")\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":"#!ek9\ndefines 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        // reassignment with : makes a 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      // An independent copy; 'localCopy <- input' would be E07930 in any function\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(\"  A sanitized parameter is never aliased (E07930)\")\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":"factorial <- pureCalculat(5)","explanation":"A misspelt function name cannot be resolved, so E50001 is reported on that line. Spell the declared function name exactly. See ek9 -h E50001 for details."},{"error":"E50001","correct":"secureResult <- pureSecureProcess(\"external input\")","incorrect":"secureResult <- pureSecureProces(\"external input\")","explanation":"A misspelt function name cannot be resolved, so E50001 is reported on that line. Spell the declared function name exactly. 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    excellent <- 90\n    poor <- 40\n    if score >= excellent\n      label: \"excellent\"\n    else if score < poor\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\nDECLARATION GUARDS\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":"#!ek9\ndefines 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, declaration guards 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  lastThree <- cat items | tail 3 | collect as List of String\n  afterFirstTwo <- 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\nApple SSL bug (2014, CVE-2014-1266): a duplicated goto bypassed SSL validation. Linux kernel (2019): auditing its switch statements for -Wimplicit-fallthrough found and fixed a number of missing break statements. CERT C MSC17-C and CWE-484 catalogue the omitted break.\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":"#!ek9\ndefines 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":["AI break continue","break continue hallucination","AI mistakes loops"],"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\nASSIGN-IF-UNSET\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 assign-if-unset. See Q168 for fallback values. See Q243 for coalescing operators. See Q281 for verifying AI code.","ek9Example":"#!ek9\ndefines 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      // === ASSIGN-IF-UNSET :=? ===\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 assign-if-unset (different semantics).","keywords":["ai","assignment","common-error","declaration","declare","equals","hallucination","infer","let","migrate","mistake","operator","pitfall","syntax","var","variable","wrong"],"primaryTopics":["AI declarations assignments","AI let confusion"],"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 guards.\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: no memory allocated to hold a value (what other languages call null) - designed never to be observable in EK9; a missing Dict key gives the getOrDefault default and an empty Optional is unset\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\nGUARDS\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 IN OTHER CONTROL FLOW\nAn if, switch or try guard stands on its own. A while or for guard must be followed by 'then' and the loop's own control, and the guard runs once, before the loop:\n  while value <- getPort() then count < limit\n    process(value)\n  for cfg <- getPort() then item in items\n    process(cfg, item)\n  switch port <- getPort()\n    case > highPort\n      useHighPort(port)\nA bare 'while value <- getPort()' or 'for entry <- nextEntry()' with no 'then' is a syntax error. A switch case compares against the guarded value itself ('case > highPort'), not one of its members ('case .port > 8000' does not parse).\n\nASSIGN-IF-UNSET 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 guards. See Q85 for guards in all control flow. See Q243 for coalescing operators. See Q281 for verifying AI code.","ek9Example":"#!ek9\ndefines 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 REPLACES NULL CHECK ===\n      // AI would write: result = findByName(\"Alice\"); if (result != null) print(result)\n      // EK9 way: guard\n\n      knownName <- \"Alice\"\n      if record <- findByName(knownName)\n        stdout.println(`Found: ${record}`)\n\n      if record <- findByName(\"Unknown\")\n        stdout.println(record)\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(name)\n      else\n        stdout.println(\"Name is unset (not null, just unset)\")\n\n      name: findByName(knownName)\n      if name?\n        stdout.println(`Name is set: ${name}`)\n\n      // === ASSIGN-IF-UNSET REPLACES NULL COALESCING ===\n      // AI would write: host = host != null ? host : \"localhost\"\n      // EK9 way: :=? assign-if-unset\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 at run time, 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, guards combine check and assignment, :=? assign-if-unset 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(knownName)","incorrect":"if findByName(knownName) != null","explanation":"EK9 has no null keyword. Use declaration guards 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?\n        stdout.println(name)","incorrect":"if name != null\n        stdout.println(name)","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 assign-if-unset 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: 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: 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: items + List() of String\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":"#!ek9\ndefines 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: List() of String","incorrect":"TaskQueue extends List of String\n      items as List of String: 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): Dict() of (String, String)","incorrect":"Settings extends Dict of (String, String)\n      entries as Dict of (String, String): 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, for Boolean, Bits and Byte).\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\nA type declares its SAMENESS once: '<=>' for an ordered type or '==' for an equatable-only type, never both. The rest ('<>' from '==', and '==' '<>' '<' '<=' '>' '>=' from '<=>') is generated; writing them for the type itself is E07181. A hand-written sameness needs a hand-written '#?' hashing what it compares, and a '#?' needs a sameness to agree with (E07184).\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":"#!ek9\ndefines 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      operator == as pure\n        -> other as Coded\n        <- rtn as Boolean: code == other.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      operator #? as pure\n        <- rtn as Integer: #? 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/Byte), 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\n        <- rtn as Integer: Integer(code)\n\n      operator == as pure\n        -> other as Coded\n        <- rtn as Boolean: code == other.code","incorrect":"hashCode() as pure\n        <- rtn as Integer: Integer(code)","explanation":"EK9 has no hashCode() method. Use the #? operator for hash code computation (and, because a type's equality and hash go together, declare '==' beside it). Without the operator, the #? prefix operator is not available on the type. See ek9 -h E07620 for details."},{"error":"E07620","correct":"operator == as pure\n        -> other as Ranked\n        <- rtn as Boolean: rank == other.rank\n\n      operator #? as pure\n        <- rtn as Integer: #? rank","incorrect":"equals() as pure\n        -> other as Ranked\n        <- rtn as Boolean: rank == other.rank","explanation":"EK9 has no equals() method. Use the == operator for equality comparison (and, because a type's equality and hash go together, declare '#?' beside it). Without the operator, the == infix operator is not available on the type. See ek9 -h E07620 for details."},{"error":"E50060","correct":"stdout.println(`Item: ${item}`)","incorrect":"stdout.println(`Item: ${item.toString()}`)","explanation":"EK9 has no toString() method, so the call cannot be resolved: E50060. Use the $ operator, which string interpolation applies for you. See ek9 -h E50060 for details."},{"error":"E50060","correct":"hash <- #?coded","incorrect":"hash <- coded.hashCode()","explanation":"EK9 has no hashCode() method, so the call cannot be resolved: E50060. Use the #? prefix operator. See ek9 -h E50060 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":"#!ek9\ndefines 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 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":["AI getters setters","getter setter","accessor AI"],"typicalErrors":[{"error":"E50060","correct":"account.owner()","incorrect":"account.getOwnerName()","explanation":"EK9 accessors are bare nouns: the method is owner(), so a Java-style getOwnerName() is not resolved on BankAccount, triggering E50060. See ek9 -h E50060 for details."},{"error":"E50060","correct":"account.summary()","incorrect":"account.getSummary()","explanation":"The accessor that returns the external state view is summary(), not getSummary(); the Java-style name is not resolved on BankAccount, triggering E50060. See ek9 -h E50060 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":"#!ek9\ndefines 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 (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      // === ASSIGN-IF-UNSET (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      testNames <- NameList()\n      testNames.add(\"Alice\")\n      testNames.add(\"Bob\")\n      expectedCount <- 2\n      assert testNames.count() == expectedCount\n      assert testNames?\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      testTag <- \">>>\"\n      fmtHandler <- (testTag) with trait of Formatter as class\n        override format()\n          -> input as String\n          <- rtn as String: `${testTag} ${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()\n      names.add(\"Alice\")","incorrect":"namesXYZ <- NameList()\n      names.add(\"Alice\")","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()\n      config :=? \"production\"","incorrect":"configXYZ <- String()\n      config :=? \"production\"","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 <- \">>>\"\n      fmtHandler <- (tag) with trait of Formatter as class","incorrect":"tagXYZ <- \">>>\"\n      fmtHandler <- (tag) with trait of Formatter as class","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    ->\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\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 still visits every row, but ':=?' assigns only while result is unset, so the first match wins (a plain ':' would let a later match overwrite it), 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":"#!ek9\ndefines 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\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":"#!ek9\ndefines 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?\n      require attempt?\n      outcome: String()\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":["retry logic","bounded retry"],"typicalErrors":[{"error":"E01070","correct":"outcome <- tryOnce(attempt)\n          result :=? outcome","incorrect":"outcome <- tryOnce(attempt)\n          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 (head is demand-driven: the filter is not evaluated again once the first match has been delivered). 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    minLongLength <- 5\n    for item in items\n      if length item > minLongLength 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    ->\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\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":"#!ek9\ndefines 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    ->\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\"\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 assign-if-unset:\n  problem <- String()\n  problem :=? validateName(name)\n  problem :=? validateAge(age)\n  problem :=? validateEmail(email)\nThe :=? means the first error wins. Once problem is set, the later :=? statements are skipped and their right-hand sides (the validator calls) are not evaluated.\n\nPATTERN 3: VALIDATE THEN PROCESS\nSeparate validation from processing entirely:\n  isValid() 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\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 guards.","ek9Example":"#!ek9\ndefines 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":"stdout is a variable of the program, not visible inside the function, so it is not resolved (E50001). This is about scope, not purity. 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 assign-if-unset 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 :=? assign-if-unset 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    ->\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\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 assign-if-unset. See Q29 for :=? semantics. See Q79 for assign-if-unset details. See Q122 for collect.","ek9Example":"#!ek9\ndefines 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 set 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: :=? assign-if-unset 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":["accumulate partial results","partial results"],"typicalErrors":[{"error":"E50001","correct":"stdout.println(`Priority (env set): ${envResult}`)","incorrect":"stdout.println(`Priority (env set): ${envResultXYZ}`)","explanation":"Using a variable name that was never declared - 'envResultXYZ' instead of 'envResult' - leaves it unresolved and triggers E50001 on that line. 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. Test the flag's value (~done), not whether it is set (~done?): Boolean(false) is already set, so ~done? is false and the loop would never run.\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":"#!ek9\ndefines 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 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":["process until condition","until condition"],"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) { if (sum + n > 100) break; sum += n; }\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: int index = 0; int taken = 0; for (String s : items) { if (index++ < 2) continue; process(s); if (++taken >= 3) break; }\nEK9 equivalent uses skip + head:\n  selected <- cat items | skip 2 | head 3 | collect as List of String\nskip drops a fixed count of leading items; head then takes the next N.\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":"#!ek9\ndefines 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) { if (sum + n > 100) break; sum += n; }\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: index = 0; taken = 0; for (s : items) { if (index++ < 2) continue; process(s); if (++taken >= 3) 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. Java null is an unset String in EK9, and the length of an unset String is itself unset (so 'length name == 0' is not true for it); test ~name? explicitly:\n  validateName() as pure\n    -> name as String\n    <- status as String: \"ok\"\n    maxNameLength <- 50\n    if ~name? or length name == 0\n      status: \"invalid\"\n    else if length name > maxNameLength\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    ->\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\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 assign-if-unset:\n  grade() as pure\n    -> score as Integer\n    <- letter as String: \"F\"\n    gradeA <- 90\n    gradeB <- 80\n    gradeC <- 70\n    if score >= gradeA\n      letter: \"A\"\n    else if score >= gradeB\n      letter: \"B\"\n    else if score >= gradeC\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":"#!ek9\ndefines 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 ~name? or 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":"The variable 'stdout' is declared in the program, not inside grade(), so the reference is unresolved (E50001). Stdout().println(...) is itself pure-callable; to print you would create or pass in a Stdout rather than refer to the program's variable. 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, arg (and arg0, arg1, ...). The check is case-insensitive, so Data, DATA, and data are all rejected. Research links names like these to slower comprehension and lower-quality code.\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":"#!ek9\ndefines 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      stdout.println(`Swap: ${swapHolder}, Done: ${isComplete}`)\n\n      sensorReading <- 42.5\n      currentItem <- \"widget\"\n      stdout.println(`Sensor: ${sensorReading}, Item: ${currentItem}`)\n\n      computedScore <- 88\n      readChunk <- \"packet contents\"\n      stdout.println(`Score: ${computedScore}, Chunk: ${readChunk}`)\n\n      // === GOOD ALTERNATIVES TO OPERATOR KEYWORD NAMES ===\n\n      nameLength <- length customerName\n      isBlank <- length customerName == 0\n      stdout.println(`Blank: ${isBlank}, Length: ${nameLength}`)\n\n      targetItem <- 2\n      hasItem <- [1, 2, 3] contains targetItem\n      positiveAmount <- 42\n      stdout.println(`Has item: ${hasItem}, Amount: ${positiveAmount}`)\n\n      needsRetry <- isRetryNeeded(retryAttempt)\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 <- 42.5\n      currentItem <- \"widget\"\n      stdout.println(`Sensor: ${sensorReading}, Item: ${currentItem}`)","incorrect":"data <- 42.5\n      currentItem <- \"widget\"\n      stdout.println(`Sensor: ${data}, Item: ${currentItem}`)","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 <- length customerName == 0\n      stdout.println(`Blank: ${isBlank}, Length: ${nameLength}`)","incorrect":"empty <- length customerName == 0\n      stdout.println(`Blank: ${empty}, Length: ${nameLength}`)","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 <- 88\n      readChunk <- \"packet contents\"\n      stdout.println(`Score: ${computedScore}, Chunk: ${readChunk}`)","incorrect":"value <- 88\n      readChunk <- \"packet contents\"\n      stdout.println(`Score: ${value}, Chunk: ${readChunk}`)","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, arg (and arg0, arg1, ...). 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 -> temporaryUser, calculationResult\n- flag, flg -> isValid, hasPermission, wasProcessed\n- data, dat -> customerData, configData, requestPayload\n- object, obj -> user, order, session\n- value, val -> price, count, threshold\n- buffer, buf -> inputBuffer, responseBuffer\n- arg -> other, item, amount, message\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":"#!ek9\ndefines 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 <- firstNumber\n      firstNumber := secondNumber\n      secondNumber := swapHolder","incorrect":"temp <- firstNumber\n      firstNumber := secondNumber\n      secondNumber := 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 <- length greeting\n      stdout.println(`Name length: ${nameLength}`)","incorrect":"length <- length greeting\n      stdout.println(`Name length: ${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 <- \"Alice:Premium:2024\"\n      stdout.println(`Record: ${customerRecord}`)","incorrect":"data <- \"Alice:Premium:2024\"\n      stdout.println(`Record: ${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":"#!ek9\ndefines 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 E08090 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":"#!ek9\ndefines 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      operator #? as pure\n        <- rtn as Integer: #? 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 E08090 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":"#!ek9\ndefines 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":["function naming","function naming conventions","naming functions"],"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":"#!ek9\ndefines 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":"z <- calculateDistanse(x, y)","explanation":"A misspelt function name ('calculateDistanse') cannot be resolved, triggering E50001. Names must be spelt consistently, which is easier to do when they are descriptive rather than single letters. 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 research links poor names to slower comprehension and lower-quality code, and real-world failures show what it costs when the meaning or limits of a value live only in someone's head.\n\nTHE RESEARCH EVIDENCE\nLawrie et al. (ICPC 2006) found full-word identifiers were understood better than single letters. Butler et al. (CSMR 2010) found flawed identifier names associated with code of lower quality. Hofmeister et al. (SANER 2017) found 72 professional developers located defects about 19% faster when identifiers were words rather than letters or abbreviations. Naming is a recurring subject of code review.\n\nREAL-WORLD DISASTERS\nMars Climate Orbiter (1999): ground software reported impulse in pound-force seconds where newton-seconds were expected, and nothing in the data said which. Therac-25 (1985-87): a race condition, and a one-byte counter that wrapped to zero and skipped a safety check, led to six massive overdoses, at least three fatal. Toyota unintended acceleration (Bookout v. Toyota, 2013): the expert review found more than 10,000 global variables in the engine control software. Ariane 5 (1996): converting the horizontal-bias value BH from a 64-bit float to a 16-bit integer overflowed, on a range assumption the code did not state.\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 code whose meaning does not live only in its author's head.\n\nSee Q290 for the banned list. See Q297 for a renaming guide. See Q144 for similar evidence-based design philosophy.","ek9Example":"#!ek9\ndefines 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 <- 212.0\n      celsiusReading <- convertTemperature(fahrenheitReading)\n      stdout.println(`${fahrenheitReading}F = ${celsiusReading}C`)","incorrect":"temp <- 212.0\n      celsiusReading <- convertTemperature(temp)\n      stdout.println(`${temp}F = ${celsiusReading}C`)","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 <- isWithinTolerance(measuredPressure, targetPressure, tolerancePercent)\n      stdout.println(`Pressure within tolerance: ${pressureOk}`)","incorrect":"flag <- isWithinTolerance(measuredPressure, targetPressure, tolerancePercent)\n      stdout.println(`Pressure within tolerance: ${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 -> temporaryUser, calculationResult\n- flag, flg -> isValid, hasPermission, wasProcessed\n- data, dat -> customerData, configData, requestPayload\n- object, obj -> user, order, session\n- value, val -> price, count, threshold\n- buffer, buf -> inputBuffer, responseBuffer\n- arg -> other, item, amount, message\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":"#!ek9\ndefines 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 <- firstPrice\n      firstPrice := secondPrice\n      secondPrice := swapHolder","incorrect":"      temp <- firstPrice\n      firstPrice := secondPrice\n      secondPrice := 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 <- length shoppingBasket == 0\n      stdout.println(`Basket empty: ${emptyBasket}`)","incorrect":"      empty <- length shoppingBasket == 0\n      stdout.println(`Basket empty: ${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 <- 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}'`)","incorrect":"      matches <- List() of String\n      for inventoryItem in inventory\n        if inventoryItem contains searchTerm\n          matches += inventoryItem\n      stdout.println(`Found ${length matches} items matching '${searchTerm}'`)","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":"#!ek9\ndefines 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":"circle <- Circl()","explanation":"A misspelt class name cannot be resolved, so E50001 is reported on that line. The name used with the sealed trait must be the declared class. 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. Removing Triangle from the list while 'Triangle with trait of Shape' is still declared triggers E05240, because Triangle is no longer a permitted implementation. See ek9 -h E05240 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":"#!ek9\ndefines 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()\n      require processor?","incorrect":"processorXYZ <- ShapeProcessor()\n      require processor?","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 E05240. See ek9 -h E05240 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\n    override doWork()\n      content <- \"Abstract\"\n      require content?\n\nBUT NOT IN THE LIST\nThe 'allow only' list may contain only concrete classes, so naming an abstract one is rejected with E05250:\n  defines module demo.sealed\n    defines trait\n      SealedTrait allow only AbstractBase, ConcreteA  //ERROR E05250\n        doWork() as abstract\n    defines class\n      AbstractBase with trait of SealedTrait as abstract\n        override doWork()\n          content <- \"Abstract\"\n          require content?\n      ConcreteA with trait of SealedTrait\n        override doWork()\n          content <- \"A\"\n          require content?\n\nThe corrected list names only the concrete class: 'SealedTrait allow only ConcreteA'.\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":"#!ek9\ndefines 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":["abstract allow only","allow only abstract"],"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":"E05240","correct":"ConcreteBeta is AbstractHelper\n      override doWork()\n        content <- \"Beta\"\n        require content?","incorrect":"ConcreteBeta is AbstractHelper\n      override doWork()\n        content <- \"Beta\"\n        require content?\n\n    UnlistedClass is AbstractHelper\n      override doWork()\n        content <- \"Unlisted\"\n        require content?","explanation":"A concrete class extending AbstractHelper inherits the sealed trait Workable, so sealing is checked transitively. UnlistedClass is not in the 'allow only' list, so the compiler reports E05240. See ek9 -h E05240 for details."},{"error":"E05240","correct":"Workable allow only ConcreteAlpha, ConcreteBeta","incorrect":"Workable allow only ConcreteBeta","explanation":"ConcreteAlpha is no longer in the 'allow only' list for the sealed trait Workable, yet it is still declared with 'with trait of Workable'. Only ConcreteBeta is permitted to implement it, so the unlisted concrete class triggers E05240. See ek9 -h E05240 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 permitted subclasses after 'allow only', then mark the class as open:\n  Shape allow only Circle, Square, Triangle as open\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 allow only Car, Truck, Sedan, Hatchback as open\n  Car extends Vehicle allow only Sedan, Hatchback as open\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":"#!ek9\ndefines 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":"E05270","correct":"Shape allow only Circle, Square, Triangle as open","incorrect":"Shape allow only Circle, Square, Triangle","explanation":"EK9 classes are closed by default, and a class with an 'allow only' list must be declared 'as open' or the list could never be used. Without 'as open' the compiler reports E05270 on the sealed class (and then each subclass is also rejected because the type is not open for extension). See ek9 -h E05270 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":"E05240","correct":"Triangle extends Shape\n      override area()\n        <- rtn as Float: 0.5","incorrect":"Triangle extends Shape\n      override area()\n        <- rtn as Float: 0.5\n\n    Pentagon extends Shape\n      override area()\n        <- rtn as Float: 1.72","explanation":"Pentagon extends Shape but is not in Shape's 'allow only Circle, Square, Triangle' list. A subclass of a sealed class that is not listed is rejected with E05240. See ek9 -h E05240 for details."},{"error":"E05240","correct":"Square extends Shape\n      override area()\n        <- rtn as Float: 1.0","incorrect":"Square extends Shape\n      override area()\n        <- rtn as Float: 1.0\n\n    Hexagon extends Shape\n      override area()\n        <- rtn as Float: 2.6","explanation":"Hexagon is declared as a subclass of Shape but the 'allow only Circle, Square, Triangle' list was not updated. Extending a sealed class with a class that is not in the list triggers E05240. See ek9 -h E05240 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":"#!ek9\ndefines 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":["sealed dispatchers","sealed exhaustive"],"typicalErrors":[{"error":"E05270","correct":"Shape allow only Circle, Square as open","incorrect":"Shape allow only Circle, Square","explanation":"Classes are closed by default in EK9, and a class with an 'allow only' list must be declared 'as open'. Without it the compiler reports E05270 on Shape (and Circle and Square are then also rejected because Shape is not open for extension). See ek9 -h E05270 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":"E05240","correct":"Square extends Shape\n      override name()\n        <- rtn as String: \"Square\"","incorrect":"Square extends Shape\n      override name()\n        <- rtn as String: \"Square\"\n\n    Triangle extends Shape\n      override name()\n        <- rtn as String: \"Triangle\"","explanation":"Triangle extends Shape but is not in 'Shape allow only Circle, Square'. A class that is not in the sealed class's 'allow only' list cannot extend it; adding Triangle without updating the list triggers E05240. See ek9 -h E05240 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_generate_dispatcher","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 has no allow only list of its own, but it is not 'as open' either, so nothing can extend it. If it were opened, any subclass would still have to be listed in Vehicle's allow only list (E05240)\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":"#!ek9\ndefines 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":["chained sealed","nested sealed"],"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, and a class with 'allow only' must be declared 'as open'. Without 'as open' the compiler reports E05270 on Vehicle (and each subclass is also rejected as the type is not open for extension). See ek9 -h E05270 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":"E05240","correct":"Truck extends Vehicle\n      override describe()\n        <- rtn as String: \"Truck\"","incorrect":"Truck extends Vehicle\n      override describe()\n        <- rtn as String: \"Truck\"\n\n    SUV extends Vehicle\n      override describe()\n        <- rtn as String: \"SUV\"","explanation":"SUV extends Vehicle but is not in 'Vehicle allow only Car, Truck, Sedan, Hatchback'. A class must be listed in every sealed ancestor's 'allow only' list; adding SUV without updating Vehicle's list triggers E05240. See ek9 -h E05240 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, the language raises a Panic: it is not an Exception, so no catch can see it, and the program ends with exit status 1 once its finally blocks and teardown have run. 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\nREQUIRE AND RETURN ORDERING (E08055)\nWhen a construct has both a require and a returning variable, declare the return UNINITIALISED (<- rtn as Type?) and assign it only AFTER the require(s). An inline-initialised return (<- rtn as Type: expr) runs BEFORE any require, so a require written to guard the return computation would be dead code - the compiler rejects that shape with E08055. Guard first, then compute. See the calculateTotal example below: the return is declared 'as Float?', the requires come first, then 'rtn: unitPrice * Float(quantity)'. The mandated 'as Type?' form also composes with E08060 - a precondition that references the return would read an uninitialised variable.\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. See Q1434 for what a Panic is. See Q1435 for the exit status of a failed require.","ek9Example":"#!ek9\ndefines 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?\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":"order <- Order(\"CUST-001\", initialItemList)","explanation":"Passing 'initialItemList' when only 'initialItems' is declared leaves the name 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, the language raises a Panic, which no catch can see. The program ends with exit status 1 after its finally blocks run.\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 in dev source files (a dev/ directory or a file named with _dev_). Using assert in any other source file is compile error E81015, and an assert in dev code that a production program can reach is E81012.\nBehavior: When the condition is false, the test fails with structured output showing file, line, column, and the failed expression text, and 'ek9 -t' exits 11.\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. If nothing catches it, the program ends with the exception's exit code, or 1 when it has none.\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 dev sources, 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. See Q1431 for how each of these sets a program's exit status.","ek9Example":"#!ek9\ndefines 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\n        balance += amount","incorrect":"require amount\n        balance += 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.reason() == \"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 thrown exception must be the given type or a subtype of it, so assertThrows(Exception, validate(badInput)) accepts a ValidationError. Asserting a child type when only a parent type is thrown fails.\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":"#!ek9\ndefines 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      default operator $\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":["assertThrows","test throws exception","test expects exception"],"typicalErrors":[{"error":"E04030","correct":"assertThrows(Exception, failingOperation())\n      stdout.println(\"Verified: failingOperation() throws Exception\")","incorrect":"assertThrows(String, failingOperation())\n      stdout.println(\"Verified: failingOperation() throws Exception\")","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":"#!ek9\ndefines 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":["assertDoesNotThrow","test no exception","verify no throw"],"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. A catch can name a specific Exception subtype, but a try has a single catch, so when several subtypes need different handling the dispatcher routes them from that ONE catch with type-safe handlers.\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":"#!ek9\ndefines 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      default operator $\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      default operator $\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_generate_dispatcher","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  first <- \"Hello\"\n  second <- \"World\"\n  third <- \"!\"\n  stdout.println(first + second + third)                   // E11068: 3 parts\n  stdout.println(first + \" \" + second + \" \" + third)       // E11068: 5 parts\n  stdout.println(second + third + \"]\")                     // E11068: 3 parts\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\nInterpolation converts non-String values (an Integer port, a Float x) to text itself, so no $ is needed inside ${...}.\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":"#!ek9\ndefines 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":["E11068","concatenation plus"],"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 Q732 for captured operator returns. See Q695 for named constants. See Q696 for complexity limits.\nSee Q728 for nesting depth boundary. See Q962 for data clump boundary. See Q731 for type traversal boundary. See Q756 for the fuzzing quality loop.","ek9Example":"#!ek9\ndefines 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":"stdout.println(`Discounted: ${salePrice}`)","incorrect":"stdout.println(`Discounted: ${salePriceXYZ}`)","explanation":"Using a variable name that was never declared - 'salePriceXYZ' instead of 'salePrice' - leaves it unresolved and triggers E50001 on that line. See ek9 -h E50001 for details."},{"error":"E50001","correct":"salePrice <- calculateDiscount(fullPrice, tenPercent)\n\n      stdout.println(`Original: ${fullPrice}`)\n      stdout.println(`Discounted: ${salePrice}`)","incorrect":"calculateDiscount(fullPrice, tenPercent)\n\n      stdout.println(`Original: ${fullPrice}`)\n      stdout.println(`Discounted: ${salePrice}`)","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 (4+ service fields and no data fields).\nE11023: Manual delegation (class implements a trait without 'by' and forwards the trait methods by hand; use 'by').\nE11024: Hybrid class (uses 'by' delegation but also has 3+ service fields).\nE11025: God class / Blob (3+ service fields AND 3+ data fields).\n\nNAMING (E11030-E11032)\nE11030: Confusingly similar names (locals, parameters or properties of one type, one edit apart - title/titel included).\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: Two or more Boolean literals passed positionally.\nE11062: Call with 4 or more positional arguments.\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: Constants scattered across 3 or more source files of a module.\nE11067: 10 or more constants spread across 2 or more files of a module.\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: Empty check always true (collection provably empty).\nE08093: Empty check always false (collection provably non-empty).\n\nSee Q312 for complexity detail. See Q313 for code smells. See Q314 for cohesion and coupling. See Q763 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":"#!ek9\ndefines 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 many 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\n      result <- gradeScore(testScore)","incorrect":"testScoreXYZ <- 85\n      result <- gradeScore(testScore)","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)\n      stdout.println(`Score ${testScore} earns grade: ${result}`)","incorrect":"resultXYZ <- gradeScore(testScore)\n      stdout.println(`Score ${testScore} earns grade: ${result}`)","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. McCabe (1976) introduced the metric and proposed 10 as a practical limit per module; 45 is EK9's ceiling, a hard upper bound rather than a target.\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: (complexity / maximum complexity) x (statements / maximum statements) must not exceed 0.50. This catches functions that are individually below each threshold but moderately high on both measures. A function with 40 cyclomatic complexity and a high statement count can still be flagged. Nesting depth is not part of this formula (it has its own limit, E11011).\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 guards. 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- guards (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":"#!ek9\ndefines 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":"mondayReport <- formatDayReport(\"Monday\", mondayNumber)","incorrect":"mondayReport <- formatDayReport(\"Monday\", mondayNumberXYZ)","explanation":"A misspelt variable (mondayNumberXYZ) at its use does not resolve, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"saturdayReport <- formatDayReport(\"Saturday\", saturdayNumber)","incorrect":"saturdayReport <- formatDayReport(\"Saturday\", saturdayNumberXYZ)","explanation":"A misspelt variable (saturdayNumberXYZ) at its use does not resolve, 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: A class with 4+ service fields and no data fields is a pure service coordinator, so declare it as a component.\nE11023 Manual delegation: A class implements a trait without 'by', has a field compatible with that trait and manually overrides the trait methods. Manual forwarding is boilerplate - use 'by' delegation.\nE11024 Hybrid class: A class uses 'by' delegation but also has 3+ service fields, mixing data-holding and service-coordination. Split it into a class and a component.\nE11025 God class (Blob): A class has 3+ service fields AND 3+ data fields, mixing state and service coordination. Split it into a component and a data class.\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. E11061 flags a call that passes two or more Boolean literals positionally, and E11062 flags a call with four or more positional arguments; name the arguments 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":"#!ek9\ndefines 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)\n      stdout.println(mailingLabel)","incorrect":"mailingLabelXYZ <- formatMailingLabel(\"Jane Smith\", homeAddress)\n      stdout.println(mailingLabel)","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. A later validation (Basili, Briand and Melo, IEEE TSE 1996, eight projects) found coupling between objects among the metrics that predicted fault-prone classes.\n\nSee Q311 for the full quality checks catalog. See Q312 for complexity metrics. See Q763 for inheritance depth.\nSee Q694 for named arguments pattern. See Q698 for excessive Boolean params.","ek9Example":"#!ek9\ndefines 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":["cohesion coupling","LCOM4"],"typicalErrors":[{"error":"E50001","correct":"reading <- TemperatureConverter(boilingPoint)","incorrect":"reading <- TemperatureConverter(boilingPointXYZ)","explanation":"The argument names a variable that was never declared, so it cannot be resolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"stdout.println(`Celsius: ${reading.celsius()}`)","incorrect":"stdout.println(`Celsius: ${readingXYZ.celsius()}`)","explanation":"The variable readingXYZ was never declared, so it cannot be resolved, triggering E50001. See ek9 -h E50001 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: the explicit operator-call form 'items.+(greeting)' used as a statement (the infix 'items + greeting' cannot be written as a statement at all) computes a new list and discards it, leaving the original list unchanged.\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 Q696 for complexity within limits. See Q700 for parameter count limits.\nSee Q732 for discarded return boundary examples.","ek9Example":"#!ek9\ndefines 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":"doubled <- doubleAmmount(originalAmount)","explanation":"A misspelt function name ('doubleAmmount') cannot be resolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"updatedItems <- items + \"first\"","incorrect":"updatedItems <- itemz + \"first\"","explanation":"A misspelt variable name ('itemz') cannot be resolved, 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: NUMERIC LITERALS AT DECISION POINTS (E11064)\nAn Integer or Float literal used as an operand of a comparison or a range is flagged. Comparisons are decision points where the meaning of the value matters most. Instead of 'if age >= 18', use a named value 'if age >= legalAdultAge'. The operators are ==, <>, !=, <, >, <=, >=, <=>, <~>, contains and matches (so 'values contains 5' is flagged too).\nNot flagged by E11064: the values 0, 1, -1, 0.0 and 1.0; every other literal type (String, RegEx, Character, Boolean, Date, Money and so on - 'word == \"admin\"' and 'word matches /ad.*/' are fine); and literals inside a constant declaration, require/assert, a constrain declaration or a named argument. A for-range's bounds (for i in 1 ... 10) are not checked.\n\nTIER 2: REPEATED STRING LITERALS (E11065)\nA plain double-quoted String literal with 3 or more characters of content that appears 3 or more times in the same file, or 4 or more times across files of the same module, is flagged. Repetition means the value has significance worth naming. Only Strings are counted: numeric, Boolean, Character and RegEx literals, backtick templates and \"\" are never counted. Literals inside a constant declaration, a list or dict literal, require/assert, a constrain declaration, a range, string concatenation or a named argument are not counted either. A constructor call is NOT exempt: label the arguments or name a shared constant.\n\nCONSTANT ORGANISATION (E11066-E11067)\nE11066: a module's constants are spread across 3 or more files - consolidate them into one file.\nE11067: a module has 10 or more constants across 2 or more files - give them a dedicated constants file.\nNeither check is about constant naming.\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":"#!ek9\ndefines 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, mandatory compiler error: numeric literals at comparisons and ranges (E11064, except 0/1/-1/0.0/1.0), and repeated String literals (E11065, 3+ per file or 4+ per module).","keywords":["E11064","E11065","clean-code","comparison","constant","literal","magic","metric","named","number","quality","repeated","value"],"primaryTopics":["magic literals","E11064","magic numbers"],"typicalErrors":[{"error":"E11064","correct":"      hotThreshold <- 30.0\n\n      if temperatureCelsius <= freezingPoint\n        classification: \"freezing\"\n      else if temperatureCelsius <= coldThreshold\n        classification: \"cold\"\n      else if temperatureCelsius >= hotThreshold","incorrect":"\n      if temperatureCelsius <= freezingPoint\n        classification: \"freezing\"\n      else if temperatureCelsius <= coldThreshold\n        classification: \"cold\"\n      else if temperatureCelsius >= 30.0","explanation":"A Float literal such as 30.0 in a comparison is a magic literal (only 0.0 and 1.0 are exempt). Name it hotThreshold so the reader knows what the value means. See ek9 -h E11064 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":"#!ek9\ndefines 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":["self-assignment","self-comparison","E08080"],"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":"#!ek9\ndefines 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":["E08091","unused capture"],"typicalErrors":[{"error":"E08091","correct":"rtn as Float: width * height","incorrect":"rtn as Float: width","explanation":"A parameter the body never reads is dead weight in the contract: height is declared but unused, so the compiler reports E08091. Either use the parameter or remove it from the signature. See ek9 -h E08091 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 an Automated Readability Index (ARI) score for each source file and reports it in the quality dashboard. This is informational only, NOT a compilation error.\n\nARI SCORES\nThe ARI score estimates the reading level needed to understand a file. It is a purely lexical measure over every token in the file (keywords and literals count too): it uses the average number of letters per token and the number of tokens per statement. It does no camelCase splitting and counts no syllables. Scores are capped at 20, and lower scores mean more readable code.\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. There is one 'Readability (ARI)' value per file in the file metrics section, not one per function or class.\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":"#!ek9\ndefines 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, one score per file shown in HTML dashboard, informational metric for continuous improvement.","keywords":["ARI","clean-code","cognitive","dashboard","identifier","informational","metric","naming","quality","readability","readable","score"],"primaryTopics":["ARI readability","readability score","identifier readability"],"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":"#!ek9\ndefines 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":"addTax(basePrice, standardTaxRate)","incorrect":"addTaxes(basePrice, standardTaxRate)","explanation":"Calling a function that is not defined (here the misspelt addTaxes) leaves the call 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     Coverage summary, then the top functions by self-time.\n  ek9 -t2p myproject.ek9    Profiling JSON in .ek9/coverage.json (for AI and CI tools).\nProfiling is reported with the coverage results, so 'p' implies -tC.\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 writes .ek9/test-results.json and .ek9/coverage.json; the 'profiling' section of coverage.json is 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":"#!ek9\ndefines 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":"E11064","correct":"thresholdForRecursion <- 2\n      if position == firstBase\n        rtn: firstBase\n      else if position == secondBase\n        rtn: secondBase\n      else if position >= thresholdForRecursion","incorrect":"if position == firstBase\n        rtn: firstBase\n      else if position == secondBase\n        rtn: secondBase\n      else if position >= 2","explanation":"The literal 2 in a comparison is a magic literal. Extract it into a named constant such as thresholdForRecursion. The literals 0 and 1 are exempt (as are -1, 0.0 and 1.0), so firstBase and secondBase are for readability only. See ek9 -h E11064 for details."},{"error":"E50001","correct":"result <- fibonacci(targetPosition)\n      output <- formatResult(\"Fibonacci\", result)","incorrect":"fibonacci(targetPosition)\n      output <- formatResult(\"Fibonacci\", result)","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\nWrites .ek9/test-results.json (test results) and .ek9/coverage.json (coverage plus profiling data). 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":"#!ek9\ndefines 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":"okCategory <- categoriseResponse(okStatusCode)","explanation":"Passing 'okStatusCode' when only 'okStatus' is declared leaves the name unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"errorCategory <- categoriseResponse(notFoundStatus)","incorrect":"errorCategory <- categoriseResponse(notFoundStatusCode)","explanation":"Passing 'notFoundStatusCode' when only 'notFoundStatus' is declared leaves the name 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":"#!ek9\ndefines 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":["Spring Autowired","autowired equivalent","spring injection"],"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":"#!ek9\ndefines 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":"stdout.println(result)","incorrect":"stdout.println(resultXYZ)","explanation":"Using a variable name that was never declared - 'resultXYZ' instead of 'result' - leaves it unresolved and triggers E50001 on that line. 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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\")\n      stdout.println(result)","incorrect":"resultXYZ <- controller.handle(\"user-123\")\n      stdout.println(result)","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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  handle()\n    -> request as String\n    <- response as String: String()\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":"#!ek9\ndefines 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":["prototype scope","request scope"],"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":"stdout.println(response1)","incorrect":"stdout.println(response1XYZ)","explanation":"A misspelt variable (response1XYZ) at its use does not resolve, 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":"#!ek9\ndefines 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":["mock injected","@MockBean"],"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":"// === TEST APPLICATION SUBSTITUTES STUB FOR PRODUCTION ===\n\n      service as NotificationService!\n      result <- service.notifyUser(\"user-42\")\n      stdout.println(result)","incorrect":"// === TEST APPLICATION SUBSTITUTES STUB FOR PRODUCTION ===\n\n      service as NotificationService!\n      resultXYZ <- service.notifyUser(\"user-42\")\n      stdout.println(result)","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 much 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 and no runtime proxy generation. When a registration carries aspects, the proxy is a concrete class generated at compile time, so the call through it is a direct call.\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, no proxy unless aspects are registered (then a compile-time proxy with a direct call), direct field access.\n\nSee Q227 for compile-time validation. See Q234 for lifecycle. See Q328 for Quarkus comparison.","ek9Example":"#!ek9\ndefines 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, no runtime proxy generation (aspect proxies, when registered, are compile-time classes), 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":"#!ek9\ndefines 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":["transaction try-with-resources","transaction close","simple transaction"],"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":"#!ek9\ndefines 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":["transaction delegation","transaction decorator","by delegate"],"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. Transaction is a trait you implement (SimpleTransaction below), not a type you construct directly:\n  executeInTransaction()\n    -> operation as BusinessOperation\n    <- result as String?\n    try\n      -> txn <- SimpleTransaction(\"auto-txn\")\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":"#!ek9\ndefines 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":["transaction higher-order","transaction callback"],"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 avoids the explicit transaction threading Go code has to do.\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    -> txnLabel as String\n    <- processor as OrderProcessor?\n    processor: (txnLabel) is OrderProcessor as function\n      if validateOrder(orderId)\n        result: `${txnLabel} | ${formatConfirmation(orderId)}`\n      else\n        result: `${txnLabel} | validation failed`\nThe capture list (txnLabel) is what makes the factory's parameter visible inside the dynamic function; an empty list () captures nothing. Downstream functions (validateOrder, formatConfirmation) 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":"#!ek9\ndefines 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      The factory: the dynamic function captures txnLabel in its closure, so the\n      processor it returns carries the transaction context with it.\n    -?>\n    createProcessor()\n      -> txnLabel as String\n      <- processor as OrderProcessor?\n      processor: (txnLabel) is OrderProcessor as function\n        if validateOrder(orderId)\n          result: `${txnLabel} | ${formatConfirmation(orderId)}`\n        else\n          result: `${txnLabel} | validation failed`\n\n  defines program\n\n    ClosureCaptureDemo()\n      stdout <- Stdout()\n\n      // === CLOSURE CAPTURE ===\n      // The factory captures the transaction label once. Everything downstream\n      // is called without a transaction parameter.\n\n      process <- createProcessor(\"TXN-001\")\n\n      result1 <- process(\"ORD-100\")\n      stdout.println(result1)\n\n      result2 <- process(\"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":["transaction closure","transaction pollution"],"typicalErrors":[{"error":"E50001","correct":"result1 <- process(\"ORD-100\")","incorrect":"result1 <- processor(\"ORD-100\")","explanation":"Calling a name that was never declared in the program (processor instead of process) is not resolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"process <- createProcessor(\"TXN-001\")","incorrect":"process <- createProcesser(\"TXN-001\")","explanation":"A misspelt factory name (createProcesser) is not resolved, 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":"#!ek9\ndefines 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":["@Transactional","transaction aspects","aop transaction"],"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 makes partial commits much harder to cause through three structural mechanisms: try-with-resources guarantees that operator close runs, there is no early return, and the isCommitted() check lets close observe the transaction state. Rolling back is still something your code does explicitly.\n\nMECHANISM 1: OPERATOR CLOSE ALWAYS RUNS\nTransaction extends Closeable. When used with try-with-resources, operator close is called automatically when the scope exits, normally or through an exception. Close is a pure operator (E07500 if it is not declared 'as pure'), so it can read isCommitted() and report or clean up pure state, but it cannot call the non-pure rollback() (E08130). A class that implements Transaction without writing its own close inherits one that does nothing, and the compiler does not complain.\n\nMECHANISM 2: NO EARLY RETURN\nEK9 has no return statement. You cannot return early from the middle of a transaction, which in other languages is one way a partial commit happens. The transaction scope always runs to completion or throws.\n\nMECHANISM 3: isCommitted() SAFETY CHECK\nYour implementation decides what isCommitted() returns. In the example it is false until commit() has been called. Operator close can check it and report an uncommitted transaction.\n\nROLLBACK IS EXPLICIT\nClose cannot call rollback(), so do it where you can: in the catch block, which still sees the transaction.\n  try\n    -> txn <- SafeTransaction(\"TXN-B\")\n    work()\n    txn.commit()\n  catch\n    -> ex as Exception\n    txn.rollback()\n\nCOMMON PARTIAL COMMIT SCENARIOS\n1. EXCEPTION BETWEEN OPERATIONS: close still runs, and the catch block can call rollback()\n2. EARLY RETURN: impossible in EK9 (no return statement)\n3. FORGOT TO COMMIT: close sees isCommitted() is false and reports it\n4. FORGOT TO ROLLBACK: rollback() has to be called explicitly (or the resource must itself discard uncommitted work when it is closed)\n\nSPRING SILENT COMMIT BUG\nIn Spring, checked exceptions silently commit the transaction. A method that throws IOException commits partial data. In EK9 close runs whatever the exception type, so an uncommitted transaction is always seen; what you do about it is explicit.\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":"#!ek9\ndefines 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: close always runs, so an uncommitted transaction is always reported.\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}: UNCOMMITTED 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 always runs and reports it ===\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 reports it\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)\n\n      stdout.println(\"Close always runs, so an uncommitted transaction is always reported\")","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 always runs and can observe isCommitted(), no return prevents mid-transaction exit, rollback() is called explicitly (close is pure and cannot call it).","keywords":["atomicity","cleanup","close","commit","dependency","inject","migrate","partial","prevent","return","rollback","safety","structural"],"primaryTopics":["partial commits","all-or-nothing transaction","half-committed"],"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":"#!ek9\ndefines 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 a well-known Spring transaction pitfall: the transaction silently does not start.\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 <- AIFriendlyTransaction(\"ai-txn\") 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- A type with no operator close used in a 'try ->' header (E07620) -> implement close. A class that declares 'with trait of Transaction' inherits a do-nothing close, so this signal is for types that are not Transactions\n- A pure operator close that calls the non-pure rollback() (E08130) -> make that call in the catch block instead\n- Type mismatch on Transaction -> fix the type\n- Unused transaction variable (E08090, an error: EK9 has no warnings) -> use or remove it\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":"#!ek9\ndefines 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":["AI transactions","AI-generated transactions","AI @Transactional"],"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":"#!ek9\ndefines 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":"E50060","correct":"currentTime <- Time().now()","incorrect":"currentTime <- Time().current()","explanation":"Time has no current() method: the factory that gives the current time is now(), called on an instance as Time().now(). An unknown method name cannot be resolved, triggering E50060. See ek9 -h Time for the full API; see ek9 -h E50060 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":"#!ek9\ndefines 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":["date formatting","date display"],"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 declaration guards 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  isParsed <- badDate?   // 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 declaration guards 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":"#!ek9\ndefines 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 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\")\n      parsedTime <- Time(\"10:30:00\")\n      parsedDT <- DateTime(\"2024-06-15T10:30:00Z\")\n\n      stdout.println(`Parsed date: ${parsedDate}`)","incorrect":"parsedDateXYZ <- 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}`)","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\"\n      if validDate <- Date(userInput)","incorrect":"userInputXYZ <- \"2024-03-20\"\n      if validDate <- Date(userInput)","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?","Calculate the duration between two dates.","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 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()    the span projected onto days\n  gap.hours()   the span projected onto hours\n  gap.years()   the span projected onto years\nThese are nominal projections: a Date - Date span is a calendar span (2024-12-25 - 2024-01-01 is P11M24D) and every month counts as 30 days, so days() gives 354 here although the real gap is 359 days (2024 is a leap year). Use them for magnitude, not as an exact day or hour count. See Q543 and Q550.\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":"#!ek9\ndefines 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","time span","timezone"],"primaryTopics":["days between dates","date gap","duration between dates","date subtraction","time span"],"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 an approximate day count (30-day months). 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\nDate + Duration is CALENDAR month arithmetic: it adds real calendar months and clamps to the month end. Because every step clamps, applying P1M twice is not the same as applying P2M once:\n  2024-01-31 + P1M gives 2024-02-29, clamped - February has no 31st\n  2024-01-31 + P1M + P1M gives 2024-03-29, clamping to Feb 29 and then adding a month\n  2024-01-31 + P2M gives 2024-03-31, one two-month calendar step\nMonth arithmetic on a Date is therefore NOT associative, exactly as in Java and Python.\n\nA Duration on its own is flat: P1M + P1M re-normalises to P2M. So combine the durations first and apply them once:\n  2024-01-31 + (P1M + P1M) gives 2024-03-31\nStep month by month only when stepwise clamping is what you mean.\n\nFor a specific day, construct the Date 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":"#!ek9\ndefines 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: Date + Duration clamps at the month end at EVERY step\n      // so base + P1M + P1M (Feb 29, then Mar 29) differs from base + P2M (Mar 31)\n      base <- 2024-01-31\n      sequential <- base + P1M + P1M\n      combined <- base + P2M\n      durationsFirst <- base + (P1M + P1M)\n      stdout.println(`Sequential P1M + P1M: ${sequential}`)\n      stdout.println(`Combined P2M: ${combined}`)\n      stdout.println(`Durations first (P1M + P1M): ${durationsFirst}`)\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; Date + Duration is calendar-aware and clamps at the month end like Java, so it is not associative, while Duration + Duration is flat (P1M + P1M = P2M): combine the durations first, then apply once.","keywords":["add","arithmetic","associative","calculate","calendar","clamping","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":"#!ek9\ndefines 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 comparison operators == <> < > <= >= and <=>. The fuzzy operator <~> is available on Date, Time, DateTime and Millisecond, but not on Duration.\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 (not on Duration)\n\nDATE COMPARISON\nDates compare chronologically:\n  newYear <- 2024-01-01\n  christmas <- 2024-12-25\n  isEarlier <- newYear < christmas   // true\n  isSame <- newYear == christmas   // false\nComparing two literals directly is rejected (E08082, the result is predetermined), so compare variables.\n\nTIME COMPARISON\nTimes compare within the day:\n  morning <- 09:00\n  evening <- 17:00\n  isEarlier <- morning < evening   // true\n\nDATETIME COMPARISON — UTC NORMALIZED\nDateTimes in different timezones compare by their UTC-equivalent instant:\n  utcTen <- 2024-06-15T10:00:00Z\n  nySix <- 2024-06-15T06:00:00-04:00\n  isSame <- utcTen == nySix   // true, 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, but what it means depends on the type. Date and Millisecond delegate to <=>, so they give -1, 0 or 1 (newYear <~> christmas is -1 and christmas <~> newYear is 1, whatever the gap). Time compares to the minute and ignores seconds, again giving -1, 0 or 1. DateTime returns the absolute number of seconds between the two instants. Duration has no <~> (using it is E07620). Use <=> for ordering and <~> only when you want one of these approximations.\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":"#!ek9\ndefines 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: operators ==, <>, <, >, <=, >=, <=> on all temporal types (<~> on every one except Duration), UTC-normalized DateTime comparison.","keywords":["after","before","chronological","compare","date","datetime","duration","equal","fuzzy","operators","sort","spaceship","time","timezone"],"primaryTopics":["compare dates","date compare","dates before after"],"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, and DATE LITERALS plus typed arithmetic for epoch dates - there is no untyped 'number of days' constructor to reach for.\n\nGETTING EPOCH TIME\n  epochNow <- SystemClock().millisecond()\nThis returns a Millisecond: the unit travels WITH the value, so it cannot be confused for seconds or nanoseconds.\n\nTHE EPOCH IS A LITERAL\n  unixEpoch <- 1970-01-01\nWrite the date you mean. A bare Integer cannot say which unit it is in, so EK9 does not offer one here.\n\nDISTANCE BETWEEN DATES IS SUBTRACTION\n  sinceEpoch <- y2k - unixEpoch\nDate - Date yields a Duration, and that Duration is a CALENDAR SPAN - P30Y for 1970-01-01 to 2000-01-01 - not a flat day count. Duration does expose days(), months() and years(), but those project the span onto a nominal 30-day month, so days() reads 10800 here rather than the 10957 real days. Use them for magnitude, not as an exact count.\n\nGOING BACK THE OTHER WAY\n  rebuilt <- unixEpoch + sinceEpoch\nDate + Duration yields a Date, and because the span kept its calendar structure the round trip back to Y2K is EXACT. Note that EK9 date arithmetic is CALENDAR-AWARE, so a Duration is not simply a day count - see Q550.\n\nMILLISECOND AS TIMESTAMP\nMillisecond is the natural type for Unix timestamps, API timestamps and database epoch values, and supports arithmetic directly.\n\nSee Q41 for Millisecond basics. See Q544 for benchmarking. See Q536 for the current date and time. See Q550 for calendar-aware date arithmetic.","ek9Example":"#!ek9\ndefines module qa.unix.timestamps\n\n  defines program\n    UnixTimestampsDemo()\n      stdout <- Stdout()\n\n      // Epoch time as a TYPED Millisecond - the unit travels with the value\n      epochNow <- SystemClock().millisecond()\n      stdout.println(`Epoch now: ${epochNow}`)\n\n      // The epoch is just a date LITERAL. EK9 has no Date(Integer): a bare number\n      // cannot say whether it means days, seconds or a year.\n      unixEpoch <- 1970-01-01\n      stdout.println(`Epoch date: ${unixEpoch}`)\n\n      y2k <- 2000-01-01\n      stdout.println(`Y2K: ${y2k}`)\n\n      // 'How far apart are two dates' is subtraction, and the answer is a CALENDAR\n      // SPAN - years/months/days - not a flat count of days\n      sinceEpoch <- y2k - unixEpoch\n      stdout.println(`Y2K since the epoch: ${sinceEpoch}`)\n\n      // days() projects that span onto a nominal 30-day month, so it is an\n      // APPROXIMATION - use it for magnitude, not as an exact day count\n      stdout.println(`Approx days, 30-day months: ${sinceEpoch.days()}`)\n\n      // Date + Duration goes back the other way, and because the span kept its\n      // calendar structure the round trip is exact\n      rebuilt <- unixEpoch + sinceEpoch\n      stdout.println(`Round-trips to Y2K: ${rebuilt == y2k}`)\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 is set: ${elapsed?}`)","migrationContext":"Java: System.currentTimeMillis(), LocalDate.ofEpochDay()/toEpochDay(). Python: time.time(), date.fromordinal() (note: ordinal counts from year 1, not 1970). JavaScript: Date.now(), new Date(epochMs). Go: time.Now().UnixMilli(). Rust: SystemTime::now().duration_since(UNIX_EPOCH). EK9: SystemClock().millisecond() returns a typed Millisecond; the epoch is the literal 1970-01-01; distances are Date - Date -> Duration. EK9 deliberately has no Date(Integer) - a bare number cannot state its unit.","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 built-in profiling of the code your tests exercise: append 'p' to a test flag.\n  ek9 -tp program.ek9 tests, coverage, then the top functions by self-time\n  ek9 -t2p program.ek9 writes the full profiling JSON to .ek9/coverage.json\n(-P is packaging, not profiling.) The 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  asDuration <- elapsed.duration()   // a 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":"#!ek9\ndefines 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 profiling by appending 'p' to a test flag (-tp, -t2p).","keywords":["benchmark","date","duration","elapsed","measure","millisecond","performance","profile","systemclock","time","timezone","timing"],"primaryTopics":["benchmark timing","measure elapsed"],"typicalErrors":[{"error":"E50001","correct":"elapsed <- endMs - startMs","incorrect":"elapsed <- endMs - startMsTypo","explanation":"A name in the subtraction that was never declared (here a mistyped startMs) is not resolved, 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":"#!ek9\ndefines 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 an easy timezone bug to make.\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:\n10:00 UTC withSameInstant New York gives 06:00 EDT.\nSame moment: both represent the same point on the timeline.\nUse 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:\n10:00 UTC withZone New York gives 10:00 EDT.\nDifferent moment: 10:00 UTC and 10:00 EDT are 4 hours apart.\nUse 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":"#!ek9\ndefines 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)\n      stdout.println(`withSameInstant NY: ${nyInstant}`)","incorrect":"nyInstantXYZ <- utcTime.withSameInstant(newYorkZone)\n      stdout.println(`withSameInstant NY: ${nyInstant}`)","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":"#!ek9\ndefines 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":["store UTC","utc storage","store timestamps"],"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    // 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":["locale dates","locale formatting levels"],"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?","Why does Jan 31 + P1M + P1M differ from Jan 31 + P2M?"],"answer":"EK9 has TWO month rules, deliberately, and which one applies depends on the types involved: Duration arithmetic is flat, while APPLYING a Duration to a Date is calendar-aware.\n\nDURATION ARITHMETIC IS FLAT (30/360)\nEvery Duration operation round-trips through total seconds and re-normalises onto a strict grid - 30 days becomes one month, 12 months becomes one year:\n  P1M is 30 days. P1Y is 360 days. Always.\n  P1M + P1M == P2M, and P1M * 12 == P1Y.\nSo Duration addition is commutative, associative and free of surprises.\n\nAPPLYING A DURATION TO A DATE IS CALENDAR-AWARE\nDate + Duration adds the duration's years/months/days through the real calendar and CLAMPS to the month end, then adds any whole days from the time part:\n  2024-01-31 + P1M = 2024-02-29   clamped - February has no 31st\n  2024-03-31 + P1M = 2024-04-30   clamped - April has no 31st\n  2024-02-29 + P1Y = 2025-02-28   clamped - 2025 is not a leap year\n\nTHE ONE THING TO WATCH: STEPPING CLAMPS AT EVERY STEP\nBecause each step clamps, applying P1M twice to a Date is NOT the same as applying P2M once:\n  2024-01-31 + P2M       = 2024-03-31   one calendar step of two months\n  2024-01-31 + P1M + P1M = 2024-03-29   clamps to Feb 29, then adds a month to the 29th\nMonth arithmetic on a DATE is therefore not associative. That is inherent to calendars - Java's LocalDate.plusMonths and Python's relativedelta behave the same way - and not an EK9 quirk.\n\nCOMBINE THE DURATIONS, THEN APPLY ONCE\nDo the Duration arithmetic first, where it IS associative, and touch the calendar only once:\n  summedFirst <- 2024-01-31 + (P1M + P1M)   // 2024-03-31\n  summedPrev <- 2023-01-31 + (P1M + P1M)   // 2023-03-31\nThis is EXACT, not a leap-year coincidence: P1M + P1M re-normalises to P2M, so it is literally the single two-month calendar step. Step separately only when stepwise clamping is what you actually mean - accruing a charge on the last day of each month, for instance.\n\nLEAP YEARS\nDate validates against the real calendar:\n  Date(2024, 02, 29) is valid - 2024 is a leap year\n  Date(2023, 02, 29) is invalid, and yields an UNSET Date rather than throwing\nA Duration's own P1Y is still 360 flat days; it is the application to a Date that consults the calendar.\n\nWHEN YOU WANT A SPECIFIC DAY\nClamping gives you the nearest valid day. When you want a particular one, build the Date from components:\n  nextMonth <- Date(base.year(), base.month() + 1, 28)\n\nSee Q32 for Duration basics. See Q540 for the duration summing pitfall introduction. See Q539 for date subtraction. See Q543 for date distances and epoch arithmetic. See Q556 for Time wrapping.","ek9Example":"#!ek9\ndefines 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      // Adding months to a DATE goes through the real calendar and CLAMPS to the month end\n      jan31 <- 2024-01-31\n      plusOneMonth <- jan31 + P1M\n      plusTwoMonths <- jan31 + P2M\n      plusOneOne <- jan31 + P1M + P1M\n      summedFirst <- 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      stdout.println(`Jan 31 + (P1M + P1M): ${summedFirst}`)\n\n      // NOT associative on a Date: the first P1M clamps to Feb 29, so the second\n      // P1M starts from the 29th - one P2M step and two P1M steps do NOT agree\n      require plusTwoMonths <> plusOneOne\n      stdout.println(`P2M step == two P1M steps: ${plusTwoMonths == plusOneOne}`)\n\n      // A Duration added to a Duration is reduced to SECONDS on a nominal 30-day\n      // month, so the Duration's own algebra IS flat, associative and 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      // and on that same flat basis P1Y is 360 days\n      oneYear <- P1Y\n      twelveMonths <- P1M * 12\n      require oneYear == twelveMonths\n      stdout.println(`P1Y == P1M * 12: ${oneYear == twelveMonths}`)\n\n      // Combining the Durations FIRST and applying once is exactly the single calendar\n      // step - P1M + P1M re-normalises to P2M - and holds in a non-leap year too\n      require summedFirst == plusTwoMonths\n      jan31Prev <- 2023-01-31\n      stdout.println(`2023: Jan 31 + P2M: ${jan31Prev + P2M}`)\n      stdout.println(`2023: Jan 31 + (P1M + P1M): ${jan31Prev + (P1M + P1M)}`)\n      require jan31Prev + P2M == jan31Prev + (P1M + P1M)\n\n      // A leap day plus a calendar year has to clamp too\n      leapPlusYear <- leapDate + P1Y\n      stdout.println(`Feb 29 2024 + P1Y: ${leapPlusYear}`)\n\n      // When you want a SPECIFIC day rather than a clamped one, build it from components\n      baseDate <- 2024-01-31\n      nextMonth <- Date(baseDate.year(), baseDate.month() + 1, 28)\n      stdout.println(`Specific day next month: ${nextMonth}`)\n\n      // Month-end again: April has no 31st, so this clamps to the 30th\n      march31 <- 2024-03-31\n      clampedToApr30 <- march31 + P1M\n      stdout.println(`Mar 31 + P1M: ${clampedToApr30}`)","migrationContext":"Java: YearMonth, LocalDate.plusMonths() clamps at the month end (Jan 31 + 1M = Feb 28) and is therefore non-associative; Period.ofMonths(1) is stored as months and applied at calculation time. Python: relativedelta(months=+1) clamps the same way. Go: AddDate(0,1,0) NORMALISES instead of clamping (Jan 31 + 1M becomes Mar 2 or Mar 3 depending on February). EK9 splits the two concerns: a Duration is flat and associative (P1M = 30 days, P1Y = 360 days), while Date + Duration is calendar-aware and clamps exactly as Java and Python do. Combine Durations first and apply once to keep the associativity.","keywords":["30 days","associative","calendar","clamping","duration","edge case","leap year","migrate","month-end","predictable","summing","time","timezone"],"primaryTopics":["leap years","month-end","calendar edge cases"],"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 DateTime writes and reads the RFC 7231 HTTP-date used by Date, Last-Modified, Expires and If-Modified-Since headers.\n\nWRITING: rfc7231()\n  httpDate <- DateTime().now().rfc7231()\nProduces the IMF-fixdate form, e.g. 'Sat, 15 Jun 2024 14:30:00 GMT'. rfc7231() always writes the SAME INSTANT in GMT (UTC), whatever the DateTime's own zone or offset - there is no need to convert first:\n  2024-12-25T00:00:00-05:00.rfc7231()   'Wed, 25 Dec 2024 05:00:00 GMT'\nDay and month names are always English, whatever the machine's locale, the day always has two digits, and the zone is always the literal GMT - exactly what RFC 7231 section 7.1.1.1 requires. A year that cannot be written in four digits gives an unset String.\n\nREADING: fromRfc7231()\n  since <- DateTime().fromRfc7231(header)\nAccepts all three forms an HTTP recipient must accept, and gives the instant in UTC:\n  IMF-fixdate   'Sun, 06 Nov 1994 08:49:37 GMT'\n  RFC 850       'Sunday, 06-Nov-94 08:49:37 GMT'   (two-digit year: more than 50 years ahead means the previous century)\n  ANSI asctime  'Sun Nov  6 08:49:37 1994'\nAnything else - an offset instead of GMT, a one-digit day in IMF-fixdate, a day name that does not match the date, lower-case names, ISO 8601 text - gives an unset DateTime, so test it with ? or a guard. For ISO 8601 text use DateTime(text).\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\nAn HTTP service does not have to write Last-Modified itself: an HTTPResponse that returns lastModified() gets the header, and If-Modified-Since is honoured with a 304, by the runtime.\n\nSee Q537 for general date formatting. See Q549 for locale formatting. See Q541 for timezone handling.","ek9Example":"#!ek9\ndefines module qa.rfc7231.http.headers\n\n  defines program\n    Rfc7231HttpHeadersDemo()\n      stdout <- Stdout()\n\n      // Date header for the current moment - rfc7231() converts to UTC itself\n      now <- DateTime().now()\n      httpDate <- now.rfc7231()\n      stdout.println(`Date header set: ${httpDate?}`)\n\n      event <- 2024-06-15T14:30:00Z\n      stdout.println(`Last-Modified: ${event.rfc7231()}`)\n\n      // Any offset or zone: the same instant is written in GMT\n      nyEvent <- 2024-12-25T00:00:00-05:00\n      stdout.println(`NY event: ${nyEvent.rfc7231()}`)\n\n      // Reading a header: all three HTTP-date forms give the same UTC instant\n      for header in [\"Sun, 06 Nov 1994 08:49:37 GMT\", \"Sunday, 06-Nov-94 08:49:37 GMT\", \"Sun Nov  6 08:49:37 1994\"]\n        since <- DateTime().fromRfc7231(header)\n        stdout.println(`Parsed: ${since}`)\n\n      // Not an HTTP-date: unset\n      notHttp <- DateTime().fromRfc7231(\"2024-06-15T14:30:00Z\")\n      stdout.println(`ISO text is an HTTP-date: ${notHttp?}`)\n\n      // Is the resource newer than the client's copy?\n      clientCopy <- DateTime().fromRfc7231(\"Sat, 15 Jun 2024 14:30:00 GMT\")\n      stdout.println(`Modified since: ${event > clientCopy}`)","migrationContext":"Java: DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)) - which writes a one-digit day and needs the UTC conversion done for it. Python: email.utils.formatdate(usegmt=True) / email.utils.parsedate_to_datetime. JavaScript: new Date().toUTCString() / Date.parse. Go: t.UTC().Format(http.TimeFormat) / http.ParseTime. EK9: dateTime.rfc7231() converts to UTC itself; DateTime().fromRfc7231(text) parses all three HTTP-date forms.","keywords":["api","date","duration","expires","format","fromRfc7231","gmt","header","http","if-modified-since","last-modified","migrate","parse","rfc7231","time","timezone","utc","web","withSameInstant"],"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  birthday <- 1971-02-01\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":"#!ek9\ndefines 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":["date promote datetime","date to datetime","datetime promotion"],"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: on a DateTime in an IANA zone (such as 'America/New_York'), adding P1D adds one calendar day, so the clock time stays 12:00 across the DST change, while adding PT24H adds 24 elapsed hours, so the clock time moves by an hour (the example shows both). A fixed offset such as -05:00 has no DST rule, so convert with withSameInstant first.\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":"#!ek9\ndefines 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.offSetFromUTC()}`)\n      stdout.println(`NY winter offset: ${nyWinter.offSetFromUTC()}`)\n\n      // MISTAKE 5: P1D is a calendar day, PT24H is 24 elapsed hours - they differ across a DST change\n      // The value must be in an IANA zone: a fixed -05:00 offset has no DST rule to apply\n      beforeUtc <- 2024-03-09T17:00:00Z\n      beforeDST <- beforeUtc.withSameInstant(newYorkZone)\n      plusOneDay <- beforeDST + P1D\n      plusTwentyFourHours <- beforeDST + PT24H\n      stdout.println(`Before DST: ${beforeDST}`)\n      stdout.println(`+ P1D: ${plusOneDay}`)\n      stdout.println(`+ PT24H: ${plusTwentyFourHours}`)\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":["daylight saving","dst bugs"],"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() returns the Date portion\n  dt.time() returns the Time portion\n\nEXTRACTION OPERATORS\n  datePart <- #< dt     // the Date (the lower/coarser component)\n  timePart <- #> dt     // the 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":"#!ek9\ndefines 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  (no Date(Integer): a bare number cannot state its unit - for epoch work use the literal\n  1970-01-01 with Date - Date -> Duration, see Q543)\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":"#!ek9\ndefines 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      fromString <- Date(\"2024-06-15\")\n      fromLiteral <- 2024-06-15\n      stdout.println(`Components: ${fromComponents}`)\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":"Date(2024, 06, 15)","incorrect":"Dates(2024, 06, 15)","explanation":"Constructing a type that does not exist (the misspelt Dates) leaves the call 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  pastMidnight <- 22:00 + PT3H   // 01:00, wraps past midnight\n  lastNight <- 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   // 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":["E08086","E08087"],"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)\nSelf-arithmetic always produces a constant: 'x - x' is always 0, 'x / x' is always 1, and 'x mod x' and 'x mod 1' are always 0 (likewise 'rem'). Arithmetic between two literals such as '3 + 2' is not flagged, and neither are 'x + x' or 'x * x'.\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":"#!ek9\ndefines 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 '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":["tautological condition","tautology","E08082"],"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\nALWAYS-FALSE ISSET CHECK (E08089)\nIf the compiler already knows a variable is unset, checking it with ? is always false. Example: 'other <- getName(); if other? ... else if other?' — in the else branch the first check has already proved 'other' is unset, so the second check can never succeed. A variable declared with '?' and never assigned is a different error: using it at all is E08020 (might be used before being initialised).\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\nA GUARD'S OWN CHECK COUNTS TOO\nA guard's gate is an isSet check you do not write as '?', so the same two errors apply to it: 'if seven <- 7' always enters (E08088) and 'if value <- Integer()' never does (E08089) - a literal type's no-argument constructor is unset. Re-testing the guard's own variable in its condition is always redundant: in 'if name <- fetchName() then name?' the condition only runs once the guard has found name set (E08088). Over a Boolean the guard enters only when the value is set AND true, so 'if ok <- check() then ok' repeats it (E08086). And an 'x?' that is one operand of an 'and' is judged like a whole condition: 'if x := 7 then x? and ready' is E08088. 'if x := fetch() then x?' - re-point an existing variable and enter only when the new value is set - stays legal, because nothing proves fetch() 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":"#!ek9\ndefines 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":["E08088","E08089"],"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."},{"error":"E08088","correct":"if name <- fetchName()","incorrect":"if name <- fetchName() then name?","explanation":"The guard already checked name: its 'then' condition runs only once the guard has found name set, so 'then name?' is always true. Drop it. See ek9 -h E08088 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 (+=, -=, *=, /=, :=:, :~:, :^:). Inside a pure scope only the declared return variable may be assigned again; any other variable is declared once with '<-' (or assigned only-if-unset with ':=?'), and reassigning it with ':' or ':=' is E08100. 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\nAssigning the return variable (rtn: working + 1). Declaring new locals with '<-'. Creating new values with non-mutating operators (+, -, *, /). Reading fields and parameters. Returning computed values.\n\nWHAT PURE FORBIDS\nMutation operators (+=, -=, *=, /=, :=:, :~:, :^:) are forbidden (E08120), even on the return variable. Reassigning any variable other than the return variable with ':' or ':=' is forbidden (E08100). 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":"#!ek9\ndefines 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 method may assign its own return variable, but no other variable;\n      //a local is declared once with '<-' and never reassigned\n      offsetBy() as pure\n        -> inputValue as Integer\n        <- rtn as Integer: 0\n        working <- inputValue * factor\n        rtn: working + 1\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      stdout.println(`3 * 10 + 1 = ${calc.offsetBy(10)}`)\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, no reassignment except of the return variable, 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":"E08100","correct":"        rtn: working + 1","incorrect":"        working: working + 1","explanation":"In a pure method only the declared return variable may be assigned again. Reassigning the local 'working' is E08100; compute into the return variable instead (or declare a new local with '<-'). See ek9 -h E08100 for details."},{"error":"E08120","correct":"        rtn: working + 1","incorrect":"        rtn += working + 1","explanation":"Mutation operators such as '+=' are forbidden in a pure scope, even on the return variable. Assign a newly computed value with ':' instead. See ek9 -h E08120 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":"#!ek9\ndefines 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":["E05150","SUPER_IS_PURE"],"typicalErrors":[{"error":"E05150","correct":"override area() as pure\n        <- rtn as Float: radius * radius * 3.14159","incorrect":"override area()\n        <- rtn as Float: radius * radius * 3.14159","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\n        <- rtn as String: `Circle with radius ${radius}`","incorrect":"describe() as pure\n        <- rtn as String: `Circle with radius ${radius}`","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":"#!ek9\ndefines 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":["E05160","SUPER_IS_NOT_PURE","non-pure parent"],"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 E08100 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 ':=?' (assign-if-unset) 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, assigning a field with ':' or ':=' is E08100, whatever the field's state (even an unset field). The ':=?' operator is allowed 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      ->\n        host as String\n        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 assign-if-unset operator.","ek9Example":"#!ek9\ndefines 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 assign-if-unset. 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, E08100 prevents reassigning fields in a pure constructor.","keywords":["E08100","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 assign-if-unset :=? must be used instead because it only assigns when the field is unset. See ek9 -h E08100 for details."},{"error":"E50001","correct":"stdout.println(`Distance: ${point.distanceFromOrigin()}`)","incorrect":"stdout.println(`Distance: ${pointXYZ.distanceFromOrigin()}`)","explanation":"A misspelt variable (pointXYZ) at its use does not resolve, 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":"#!ek9\ndefines 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        this.yPos :=? 0.0\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)\n      coord2 <- Coordinate(5.0)\n      stdout.println(`Coord1: (${coord1.xPos()}, ${coord1.yPos()})`)","incorrect":"coord1XYZ <- Coordinate(3.0, 4.0)\n      coord2 <- Coordinate(5.0)\n      stdout.println(`Coord1: (${coord1.xPos()}, ${coord1.yPos()})`)","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    ->\n      street as String\n      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    -> host as String\n    this(host, 8080)\n  Config() as pure\n    this(\"localhost\", 8080)\n  Config() as pure\n    ->\n      host as String\n      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":"#!ek9\ndefines 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 assign-if-unset :=? 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":"#!ek9\ndefines 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":"proc <- Procesor(\"APP\")","explanation":"A misspelt type name ('Procesor') cannot be resolved, 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":"#!ek9\ndefines 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":"Labrador extends Dog\n      override sound() as pure","incorrect":"Labrador extends Dog\n      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."}],"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    <- rtn as String: ...\n\nCOMPARISON IS GENERATED\nA type declares its sameness once ('<=>' here); '==', '<>', '<', '<=', '>' and '>=' are generated from it, pure, and inherited by subtypes, which may not redeclare it.\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":"#!ek9\ndefines 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: the sameness, == and <> are generated from it\n      operator <=> as pure\n        -> other as Temperature\n        <- rtn as Integer: degrees <=> other.degrees\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":"#!ek9\ndefines 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\n        <- rtn as Float: width * height","incorrect":"      override measure()\n        <- rtn as Float: width * height","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\n        <- rtn as String: `Box ${width}x${height}`","incorrect":"      override label()\n        <- rtn as String: `Box ${width}x${height}`","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 E05120.\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":"#!ek9\ndefines 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, E05120 if missing.","keywords":["E05120","abstract","child","explicit","inherit","method","open","override","parent","require","virtual"],"primaryTopics":["override","method override"],"typicalErrors":[{"error":"E05120","correct":"override speed() as pure\n        <- rtn as String: \"fast\"","incorrect":"speed() as pure\n        <- rtn as String: \"fast\"","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()\n      stdout.println(`Vehicle: ${vehicle.describe()}, ${vehicle.speed()}`)","incorrect":"vehicleXYZ <- Vehicle()\n      stdout.println(`Vehicle: ${vehicle.describe()}, ${vehicle.speed()}`)","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 E05120 does not override?","Why does the compiler reject my method that matches the parent?","How do I fix E05120 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 E05120. This prevents accidental method shadowing.\n\nTHE ERROR: E05120\nE05120 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. Before (error):\n  defines class\n    Parent as open\n      process()\n        <- rtn as String: \"parent\"\n    Child extends Parent\n      process()    // ERROR E05120\n        <- rtn as String: \"child\"\nAfter (correct):\n  defines class\n    Parent as open\n      process()\n        <- rtn as String: \"parent\"\n    Child extends Parent\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":"#!ek9\ndefines 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, E05120 reported if missing.","keywords":["E05120","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":"farewell() has the same name and signature as Greeter.farewell(), so the override keyword is required; omitting it is E05120. See ek9 -h E05120 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 E05110 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 E05110. This catches typos and stale overrides.\n\nTHE ERROR: E05110\nE05110 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. Before (error E05110):\n  override newMethod()\n    <- rtn as String: \"new\"\nAfter (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. Before (typo, E05110):\n  override procss()\nAfter (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\nE05110 is a safety net against stale code. When a parent method is renamed, all child overrides immediately break with E05110, 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":"#!ek9\ndefines 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: E05110 when override has no matching parent method.","keywords":["E05110","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 override must use EXACTLY the parent's access modifier. You can neither narrow it nor widen it. The compiler reports E05130 for any difference.\n\nTHE RULE\nPublic parent method requires a public override. Protected parent method requires a protected override. Private methods cannot be overridden (not visible to children).\n\nTHE ERROR: E05130\nE05130 ('methods with same signature have different access modifiers') fires when an override's access modifier differs from the parent's in either direction - a public parent method made protected in the child, or a protected parent method made public.\n\nWHY THIS RULE\nOne method, one contract: the override is the same method as the parent's, so it keeps the same visibility. Narrowing would break code that calls the parent method polymorphically; widening would quietly publish what the parent chose to keep inside the hierarchy.\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, and writing 'override private' is E07010 (remove 'override' when using 'private').\n\nSee Q570 for override basics. See Q572 for false override claims. See Q95 for field visibility.","ek9Example":"#!ek9\ndefines 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: keep it protected - an override uses exactly the parent's\n      //access modifier; widening it to public is E05130 as well\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: an override may widen visibility (protected to public) but not reduce it. Kotlin and C#: similar, with C# requiring the same access for overrides. EK9: stricter than Java - the override's access modifier must match the parent's exactly (E05130 for narrowing or widening).","keywords":["E05130","abstract","access","function","inherit","modifier","open","override","private","protected","public","restrictive","virtual","visibility"],"primaryTopics":["E05130","override access modifier","override visibility rules"],"typicalErrors":[{"error":"E07010","correct":"override process() as pure","incorrect":"override private process() as pure","explanation":"A private method cannot be an override: private methods are not inherited, so 'override' and 'private' together are rejected. Keep the parent's access modifier (public here). See ek9 -h E07010 for details."},{"error":"E07010","correct":"override protected validate() as pure","incorrect":"override private validate() as pure","explanation":"'override private' is rejected outright: a private method is never an override. The override of a protected parent method must itself be protected. 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":"#!ek9\ndefines 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":["E05030","closed extend"],"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":"#!ek9\ndefines 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\n        <- rtn as String: \"land transport\"","incorrect":"describe() as pure\n        <- rtn as String: \"land transport\"","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":"Truck is declared without 'as open', so no type may extend it: extending a type that is not open is E05030 (here it would also be circular, since Truck already extends LandTransport). 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 MUST AGREE\nThe parent method and the trait method must agree on 'pure'. If one is pure and the other is not, the compiler raises E05290 ('parents disagree on pure') - no single override can honour both a pure and a non-pure contract, so fix the parents. When they agree, the override matches that shared purity: a pure parent requires a pure override (E05150), and a non-pure parent forbids marking the override pure (E05160).\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":"#!ek9\ndefines 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_methods","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\nCOMPARISON IS NOT OVERRIDDEN\nSameness ('<=>' or '==') is declared once per hierarchy and sealed: when a parent defines operator <=>, the child inherits it (and the generated '==', '<>', '<', '<=', '>', '>=') and may not redeclare '<=>', '==' or '#?' (E07182). A different ordering is a comparator function given to sort, not a second '<=>'.\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":"#!ek9\ndefines 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      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":["override operators","operator override","operator inheritance"],"typicalErrors":[{"error":"E05120","correct":"override operator $ as pure\n        <- rtn as String: `${amount()} kg`","incorrect":"operator $ as pure\n        <- rtn as String: `${amount()} kg`","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\n        <- rtn as String: `${amount()} kg`","incorrect":"override operator $\n        <- rtn as String: `${amount()} kg`","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":"#!ek9\ndefines 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":"E05030","correct":"Rectangle extends Shape as open","incorrect":"Rectangle extends Shape","explanation":"Square extends Rectangle, so Rectangle must be declared 'as open'. A class without 'as open' (or 'as abstract') is closed and cannot be extended - E05030. See ek9 -h E05030 for details."},{"error":"E05120","correct":"override area() as pure\n        <- rtn as Float: rectWidth * rectHeight","incorrect":"area() as pure\n        <- rtn as Float: rectWidth * rectHeight","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\n        <- rtn as Float: rectWidth * rectHeight","incorrect":"override area()\n        <- rtn as Float: rectWidth * rectHeight","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":"#!ek9\ndefines 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\n        <- rtn as String: message","incorrect":"identity() as pure\n        <- rtn as String: message","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    holder <- String()\n    balance <- 0.0\n    Account()\n      -> holder as String\n      this(holder, 0.0)  //delegates to two-arg constructor\n    Account()\n      ->\n        holder as String\n        balance as Float\n      this.holder: holder\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":"#!ek9\ndefines 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 extends Parent\n    age as Integer?\n    Child()\n      ->\n        name as String\n        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 extends Parent\n    ChildClass()\n      ->\n        a as String\n        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":"#!ek9\ndefines 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  defines class\n    Child extends Parent\n      Child()\n        -> value as Integer\n        super(value)       // First statement\n        require 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":"#!ek9\ndefines 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  defines class\n    Animal as open\n      species <- String()\n      Animal()\n        -> species as String\n        this.species: species\n      default operator ?\n    Pet extends Animal\n      petName <- String()\n      Pet()\n        -> petName as String\n        this(petName, \"Unknown\")      // this() delegation\n      Pet()\n        ->\n          petName as String\n          species as String\n        super(species)                // super() delegation\n        this.petName: petName\n      default operator ?\n\nThe one-arg Pet 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":"#!ek9\ndefines 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":["this and super","E05060","constructor this super"],"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 E05040 super without explicit parent?","When must I call super() in a constructor?","Does EK9 have implicit super() calls?"],"answer":"super() is only needed when you want the parent initialised from arguments. If the parent has a no-arg constructor (declared, or the one EK9 synthesises), EK9 implicitly calls it. E05040 fires if you call super() or use super.member in a class without an explicit parent class. E05080 is the 'inappropriate use of super' error, for example super(...) used as a property initialiser.\n\nWHEN SUPER() IS NEEDED\nWhen the parent's only declared constructor takes parameters, EK9 still synthesises a public no-arg constructor for it. A child constructor that omits super() therefore compiles - the implicit super() reaches that no-arg constructor - but the parent's properties are left unset (here entityName). Call super(entityName) to give the parent its value. Only when the parent's no-arg constructor is private is omitting super() a compile error (E07176/E07177).\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\nSUPER MEMBER ACCESS WITHOUT A PARENT\nUsing the 'super' keyword for member access ('super.entityName()') in a class that does not extend another is also E05040.\n\nE05080: INAPPROPRIATE USE OF SUPER\nsuper(...) written as a property initialiser, for example 'petName as String: super(\"x\")', is reported as 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":"#!ek9\ndefines 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 calls super(entityName) so the parent is given its value;\n    //omitting it would still compile but leave entityName unset\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 for super in a parentless class, E05080 for super(...) as a property initialiser.","keywords":["E05040","E05080","constructor","delegation","explicit","implicit","missing","parent","super","this"],"primaryTopics":["E05040","E05080"],"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":"A field in EK9 is either initialized inline at declaration, or declared 'as T?' (no value) and then set by every public constructor. Constructors can also reassign inline-initialised fields using ':' or ':=?'. Understanding the interaction between field defaults and constructor assignment is essential.\n\nFIELD DEFAULTS\nGive a field an inline default:\n  name <- String()    //initialized to unset String\n  count <- 0          //initialized to 0\nThe alternative is 'name as String?' with no value, which needs the default constructor made private and every public constructor to set it (see Q886). A plain 'name as String' with no '?', no initialiser and no injection '!' is 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":"#!ek9\ndefines 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: a field is initialized inline or declared 'as T?' and set by every public constructor; E08180 for a plain 'as T' with neither; 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":"A property initialiser cannot be another property or a complex expression - this is E04050 even when the referenced property is declared first. Initialise with a literal or a constructor call, and derive values from other properties inside a constructor. 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 the constructors of ONE class are consistently pure - all or none (E05190) - and that a pure constructor only calls pure constructors (E08130).\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    ->\n      host as String\n      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    ->\n      name as String\n      extra as Integer\n    super(name)\n    this.extra :=? extra\n\nCONSISTENCY REQUIREMENT\nE05190 applies WITHIN one class: if any constructor is marked pure, all of that class's constructors must be. It says nothing about a parent. A pure constructor may only delegate to pure constructors (this(...) or super(...)); a pure child calling a non-pure parent constructor is E08130. The reverse is fine: a non-pure child (all of its constructors non-pure) may extend a class with pure constructors.\n\nASSIGN-IF-UNSET\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":"#!ek9\ndefines 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 of a class must be consistently pure (E05190), a pure constructor may only call pure constructors (E08130), :=? 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  - the Employee constructor takes name, age and role, and calls super(name, age) so Person gets name and age\n  - the Person constructor takes name and age, and calls super(name) so Entity gets name\n  - the Entity constructor takes name and stores it with this.entityName: entityName\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":"#!ek9\ndefines 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  defines 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":"#!ek9\ndefines 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  defines function\n    MathOp as pure abstract\n      ->\n        a as Float\n        b as Float\n      <- result as Float?\n\n    AddOp is MathOp as pure\n      ->\n        a as Float\n        b as Float\n      <- result as Float: a + b\n\n    SubOp is MathOp as pure\n      ->\n        a as Float\n        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":"#!ek9\ndefines 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":"E08120","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      operandA += operandB","explanation":"A function declared as pure must not mutate variables. The += operator mutates operandA in place, so the compiler reports E08120. Printing is allowed from pure code; changing state is not. See ek9 -h E08120 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  defines function\n    Formatter as pure abstract\n      -> text as String\n      <- rtn as String?\n\nThen create an implementation inline:\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":"#!ek9\ndefines 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":["dynamic function extends","dynamic abstract function","extend abstract function"],"typicalErrors":[{"error":"E08120","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      text += \"!\"","explanation":"A function declared as pure must not mutate variables. The += operator mutates text in place, so the compiler reports E08120. See ek9 -h E08120 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\nPure functions cannot mutate state, and a function has no fields of its own. A non-pure dynamic function can mutate a captured object, and because the capture shares the SAME object with the enclosing variable (it is not a copy), a mutating operator such as += is visible on both sides. 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":"#!ek9\ndefines 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":["function vs class hierarchy","function hierarchies","stateless hierarchy"],"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. 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":"#!ek9\ndefines 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      -> text as String\n      <- output as String: text.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":["E05140","extension signature mismatch"],"typicalErrors":[{"error":"E08120","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      first += second","explanation":"A function declared as pure must not mutate variables. The += operator mutates first in place, so the compiler reports E08120. See ek9 -h E08120 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. Functions are closed by default, like classes: a concrete function can only be extended when it is declared 'as open'.\n\nA MULTI-LEVEL CHAIN\nAn abstract function can extend another abstract function, and a concrete function at the end of the chain implements it:\n  defines function\n    BaseOp as pure abstract\n      -> x as Integer\n      <- rtn as Integer?\n\n    PositiveOp is BaseOp as pure abstract\n      -> x as Integer\n      <- rtn as Integer?\n\n    DoubleOp is PositiveOp as pure\n      -> x as Integer\n      <- rtn as Integer: x + x\n\nCONCRETE FUNCTIONS ARE CLOSED BY DEFAULT\nA function that provides an implementation cannot be extended unless it is declared 'as open' (for example DoubleOp is PositiveOp as pure open). Extending a closed function is error E05030, not open to be extended - the same rule as classes.\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":"#!ek9\ndefines 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    //A second abstract level: extends Filter and is itself extended below\n    MinLengthFilter is 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 a concrete function three levels from the top of this chain\n    LongEnoughFilter is MinLengthFilter 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 unless declared as open.","keywords":["abstract","chain","closed","concrete","extend","extension","function","hierarchy","multi-level"],"primaryTopics":[],"typicalErrors":[{"error":"E08120","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      input += \"x\"","explanation":"A function declared as pure must not mutate variables. The += operator mutates input in place, so the compiler reports E08120. See ek9 -h E08120 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":"Functions are closed by default. An abstract function can always be extended; a concrete function (one with an implementation body) can be extended only when it is declared 'as open'. Extending a closed function is error E05030, not open to be extended.\n\nCAN EXTEND: ABSTRACT FUNCTIONS\nFunctions declared with 'as abstract' can be extended:\n  defines function\n    TextCheck as pure abstract\n      -> item as String\n      <- result as Boolean?\n\nCLOSED BY DEFAULT: CONCRETE FUNCTIONS\nFunctions with a body cannot be extended unless they are declared 'as open':\n  defines function\n    addOne() as pure\n      -> x as Integer\n      <- rtn as Integer: x + 1\n    //Cannot write: DoubleAddOne is addOne - E05030, not open to be extended\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 must not be pure either (E05160).\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":"#!ek9\ndefines 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    PrefixedConverter is Converter as pure\n      -> input as Integer\n      <- output as String: \"0x\" + $input\n\n    //Concrete function: closed by default, so it 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 += PrefixedConverter\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: abstract functions can be extended, concrete functions are closed unless declared as open (E05030), E05140 for signature mismatch.","keywords":["E05140","E05150","abstract","closed","concrete","extend","extension","function","immutable","purity","rules","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E08120","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      number += number","explanation":"A function declared as pure must not mutate variables. The += operator mutates number in place, so the compiler reports E08120. See ek9 -h E08120 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  defines function\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  defines function\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":"#!ek9\ndefines 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":"E08120","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      text += \"x\"","explanation":"A function declared as pure (here by the purity contract of the abstract function) must not mutate variables. The += operator mutates text in place, so the compiler reports E08120. See ek9 -h E08120 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":"#!ek9\ndefines 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 has no primitives: every value, an Integer included, is an object passed by reference. A mutating operator on a parameter (+=, etc.) therefore changes the caller's value, while reassigning a parameter is rejected (E08110). In a pure function a parameter cannot be mutated at all (E08120).\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":"#!ek9\ndefines 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":["function parameters","parameter passing","function arguments"],"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    ->\n      maxRetries as Integer\n      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":"#!ek9\ndefines 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        require maxRetries?\n        this(maxRetries, 30)","explanation":"The this() delegation call must be the very first statement in the constructor body. No code, not even a require, can execute before delegation. See ek9 -h E05050 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":"#!ek9\ndefines 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 costs 0, each class inheritance step costs 0.05, each trait step costs 0.10, a coercion such as Integer to Float costs more (0.5 plus a conversion cost), and an Any match is by far the most expensive. 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: \"Any type\"\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\nFUNCTIONS DO NOT DISPATCH\nStandalone functions cannot share names in the same module (E01030), so there is neither function overloading nor function dispatch: 'dispatcher' applies to methods only. For polymorphism between functions use an abstract function with several implementations. Method 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":"#!ek9\ndefines 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 (declared List of Any): runtime dispatch shines here\n      items as List of Any: [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":"E08120","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        item += \"x\"","explanation":"A method declared as pure must not mutate variables. The += operator mutates item in place, so the compiler reports E08120. See ek9 -h E08120 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  defines function\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":"#!ek9\ndefines 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 (written here as open, since a closed class could not be extended at all, which is E05030):\n  defines class\n    A extends A as open  //ERROR: E05020 - A depends on itself\n      default operator ?\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  defines class\n    A extends B as open  //ERROR: E05020 - cycle A -> B -> A\n      default operator ?\n    B extends A as open  //ERROR: E05020 - cycle A -> B -> A\n      default operator ?\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.\nSee Q763 for inheritance depth boundary example.","ek9Example":"#!ek9\ndefines 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":["circular inheritance","extend itself","E05020"],"typicalErrors":[{"error":"E05030","correct":"Car extends Vehicle as open","incorrect":"Car extends ElectricCar as open","explanation":"ElectricCar is not declared open, and types are closed by default, so Car cannot extend it: E05030 (not open to be extended). Only if ElectricCar were also open would the compiler go on to report the cycle Car -> ElectricCar -> Car as E05020. See ek9 -h E05030 for details."},{"error":"E05120","correct":"override kind()\n        <- rtn as String: \"car\"","incorrect":"kind()\n        <- rtn as String: \"car\"","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 the TYPE_HIERARCHY_CHECKS phase, 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 Q763 for inheritance depth quality checks.","ek9Example":"#!ek9\ndefines 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()\n        <- rtn as String: \"wolf\"","incorrect":"describe()\n        <- rtn as String: \"wolf\"","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":"#!ek9\ndefines 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":"What does 'this' mean inside a standalone function, and why can't I reach fields through it?","url":"https://ek9.io/qa/QA0605.html","alternatePhrasings":["Can I use this.field inside a function in EK9?","Where is 'this' valid in EK9?","Why does using 'this' in a function cause an error?"],"answer":"Inside a standalone function, 'this' is legal - it refers to the function itself (a function is an object in EK9, so it can be passed around as a value). What a function does NOT have is an instance with fields, so 'this.count' in a function fails: there is no 'count' on the function, and the compiler reports E50001 (not resolved).\n\nWHAT 'this' REFERS TO\n- In class and record methods, constructors and operators: the current object, so 'this.count' reaches its fields.\n- In a dynamic class body: that dynamic class instance.\n- In a standalone function: the function itself. 'me <- this' compiles; 'this.count' does not.\n\nE05070 IS SOMETHING ELSE\nE05070 is about CALLING 'this()' where there is no constructor to delegate to - inside a function - or assigning to 'this' where that is not allowed. (Calling 'this()' in a method is E05060.) Merely naming 'this' in a function is not an error.\n\nCOMMON MISTAKE\nDevelopers coming from Java or C# sometimes write standalone functions and use 'this' as if they were methods with access to instance state. In EK9, functions only have their parameters, their locals and (for dynamic functions) their 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":"#!ek9\ndefines 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: 'this' would be the function itself, which has\n    //no fields - so work from 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 fields reached through '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' in a method is the object; in a standalone function it is the function itself, so 'this.field' there is E50001 (not resolved).","keywords":["E50001","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":"Inside a standalone function 'this' is the function itself, which has no 'count' field, so this.count cannot be resolved. Pass the value in as a parameter. 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 handle it?","url":"https://ek9.io/qa/QA0606.html","alternatePhrasings":["What is E05120 in EK9?","How does EK9 detect method shadowing?","Can a child class have a method with the same name but a different signature in EK9?"],"answer":"'Shadowing' usually means a child class declaring a method that silently replaces or hides a parent method. EK9 splits this into three cases and decides each one by the SIGNATURE (name plus parameter types):\n\n1. SAME NAME, SAME SIGNATURE, NO 'override' - E05120\nThe child method would replace the parent's, but without saying so. EK9 rejects it with E05120: an accidental override is not allowed. Add 'override' if replacement is what you mean, or rename the method if it is not.\n\n2. SAME NAME, SAME SIGNATURE, WITH 'override' - a proper override\nCalls through a parent-typed reference reach the child's version: the object decides which method runs.\n\n3. SAME NAME, DIFFERENT SIGNATURE - an overload, which is legal\nA child may add format(text, prefix) beside the inherited format(text). Both are available on the child; a call picks the one whose parameters match. It is NOT an override, so marking it 'override' is E05110 (it does not override anything).\n\nWHY THIS MATTERS\nIn Java, forgetting @Override means a typo in a parameter type silently creates a new method, and a matching signature without the annotation silently overrides. EK9 makes the intent explicit: an override must say 'override' (E05120 otherwise), and 'override' must really match a parent signature (E05110 otherwise).\n\nWHAT TO DO\nIf replacing parent behaviour: use 'override' with the EXACT same parameter types. If adding behaviour: an overload with different parameters is fine, or use a distinct, descriptive name when the meaning differs.\n\nSee Q570 for override basics. See Q571 for missing override keyword. See Q572 for false override claims.","ek9Example":"#!ek9\ndefines 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: an overload - same name, different parameters, no 'override'\n      format() as pure\n        ->\n          text as String\n          prefix as String\n        <- rtn as String: prefix + text.upperCase()\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      require upper.format(\"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: a matching signature overrides with or without @Override; a different signature is an overload. C++: hiding is allowed by default, 'override' keyword optional. C#: 'new' keyword can explicitly shadow (EK9 has no such keyword). Python: no override concept, last definition wins. Kotlin: 'override' required, similar to EK9. Swift: 'override' required, similar to EK9. EK9: a same-signature method without 'override' is E05120; 'override' on a different signature is E05110; a different signature without 'override' is a legal overload.","keywords":["E05120","abstract","child","circular","collision","function","hierarchy","inherit","name","open","overload","override","parent","shadow","shadowing","signature","substitution","type","virtual"],"primaryTopics":["E05120","shadow prevention"],"typicalErrors":[{"error":"E05120","correct":"override format() as pure","incorrect":"format() as pure","explanation":"UpperFormatter.format has the same signature as Formatter.format, so it replaces it and must say so with 'override'. Without it, the compiler reports E05120. 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":"#!ek9\ndefines 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":["E05270","allow only open","sealed class open"],"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()\n        <- rtn as String: \"circle\"","incorrect":"name()\n        <- rtn as String: \"circle\"","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":"#!ek9\ndefines 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()\n        <- rtn as String: \"car\"","incorrect":"describe()\n        <- rtn as String: \"car\"","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":"#!ek9\ndefines 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()\n        <- rtn as String: \"xml\"","incorrect":"serialize()\n        <- rtn as String: \"xml\"","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":"#!ek9\ndefines 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":["refactor circular composition","composition delegation refactor"],"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() AND SUPER() CALLS (E05060, E05070)\nConstructor delegation - calling 'this()' or 'super()' - belongs in constructors: in a method it is E05060, and calling 'this()' inside a function, which has no constructors at all, is E05070. (Naming 'this' in a function is legal: it is the function itself.)\n\nOVERRIDE VALIDATION (E05100-E05120)\nE05100: 'override' on a member of something nothing can extend (such as a program or service). E05110: 'override' on a method that has no parent version with the same signature. E05120: a method with the same name AND signature as a parent method, but without 'override'. A same-named method with DIFFERENT parameters is a legal overload.\n\nPURITY HIERARCHY (E05150-E05160)\nE05150: overriding a pure method without marking the override as pure. E05160: marking an override as pure when the parent version is NOT pure.\n\nSEALED TYPE ENFORCEMENT (E05240-E05270)\nE05240: a type not named in the 'allow only' list extends the sealed type. 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.\nSee Q763 for inheritance depth boundary example.","ek9Example":"#!ek9\ndefines 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","E05060","E05110","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":"UserEntity.entityId has the same signature as BaseEntity.entityId, so it replaces it and must say so with 'override'. Without it, the compiler reports E05120. 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?","Can a non-pure dispatcher have pure handlers?"],"answer":"A dispatcher has ONE purity, set by its entry point, and every handler must match it. If the entry is 'as pure', every handler must also be 'as pure'. If the entry is not pure, no handler may be 'as pure'. Either mismatch raises E05170, and the message says which way it failed.\n\nWHY THE HANDLERS MUST MATCH\nCallers only ever see the entry point; the dispatcher routes their call to whichever handler matches the argument's runtime type. The entry point's purity is therefore the promise made about every handler. A non-pure handler would break a pure entry point's promise, and a pure handler behind a non-pure entry point is a claim no caller can rely on.\n\nPURE DISPATCHER PATTERN\nMark the entry and all handlers as pure:\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\nNON-PURE DISPATCHER PATTERN\nOmit 'as pure' from the entry AND from every handler:\n  LoggingCatalog\n    label() as dispatcher\n      -> item as Item\n      <- rtn as String: item.name()\n\n    label()\n      -> item as Book\n      <- rtn as String: \"Book: \" + item.name()\n\nBOTH DIRECTIONS ARE FORBIDDEN\nYou cannot have a pure entry with a non-pure handler, nor a non-pure entry with a pure handler. When a handler needs side effects, the whole dispatcher is not pure; when the whole dispatcher should be pure, every handler is.\n\nSee Q560 for purity basics. See Q566 for pure method restrictions. See Q60 for dispatcher fundamentals. See Q620 for dispatcher hierarchy rules.","ek9Example":"#!ek9\ndefines 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    //CORRECT: a dispatcher that is not pure, with no pure handler\n    LoggingCatalog\n      labelled as Integer: 0\n\n      label() as dispatcher\n        -> item as Item\n        <- rtn as String?\n        labelled++\n        rtn: item.name()\n\n      label()\n        -> item as Book\n        <- rtn as String: \"Book: \" + item.name()\n\n      default operator ?\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\"\n\n      logging <- LoggingCatalog()\n      require logging.label(book) == \"Book: EK9 Guide\"\n      require logging.label(mag) == \"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 requires every handler to match the purity of the dispatcher entry point - all pure, or none pure.","keywords":["E05170","ambiguity","both-directions","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":"E05170","correct":"label()\n        -> item as Book","incorrect":"label() as pure\n        -> item as Book","explanation":"The dispatcher entry label is not pure, so no handler may be pure either. Marking one handler as pure creates a purity mismatch: callers only see the entry point, so the handler's promise is one they can never rely on. See ek9 -h E05170 for details."},{"error":"E05210","correct":"describe() as pure\n        -> item as Magazine\n        <- rtn as String: \"Magazine: \" + item.name()","incorrect":"describe() as pure\n        -> item as String\n        <- rtn as String: item","explanation":"Each handler parameter type must be in the hierarchy of the dispatcher entry parameter type Item. A handler for an unrelated type like String can never be reached. See ek9 -h E05210 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":"#!ek9\ndefines 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":["E05180","dispatcher private handler","dispatcher parent private"],"typicalErrors":[{"error":"E05210","correct":"process()\n        -> msg as TextMessage\n        <- rtn as String: \"Text: \" + msg.content()","incorrect":"process()\n        -> msg as String\n        <- rtn as String: \"Text: \" + msg","explanation":"The dispatcher entry parameter is Message. Every handler parameter must be a subtype of Message. String is not in the Message hierarchy, so the handler could never be selected. See ek9 -h E05210 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    <- rtn as String: \"animal\"\n  handle()\n    -> a as Dog        //OK: Dog extends Animal\n    <- rtn as String: \"dog\"\n  handle()\n    -> a as Cat         //OK: Cat extends Animal\n    <- rtn as String: \"cat\"\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":"#!ek9\ndefines 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":["E05210","dispatcher unrelated subtype"],"typicalErrors":[{"error":"E05210","correct":"handle()\n        -> animal as Dog\n        <- rtn as String: \"Dog says: \" + animal.speak()","incorrect":"handle()\n        -> animal as String\n        <- rtn as String: \"Dog says: \" + animal","explanation":"The dispatcher entry takes Animal. A handler parameter type must be a subtype of Animal. String is an existing type that is completely unrelated, so the handler would never match at runtime. See ek9 -h E05210 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":"#!ek9\ndefines 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":"E05210","correct":"describe()\n        -> shape as Rectangle\n        <- rtn as String: \"Rectangle with four sides\"","incorrect":"describe()\n        -> animal as String\n        <- rtn as String: \"Rectangle with four sides\"","explanation":"The dispatcher entry dispatches on Shape. A handler for an unrelated type such as String is outside the Shape hierarchy and can never match. See ek9 -h E05210 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":"#!ek9\ndefines 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":["dispatcher ambiguity detection","dispatcher tie","ambiguous dispatcher"],"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":"E05210","correct":"process()\n        -> vehicle as Truck\n        <- rtn as String: \"Truck: \" + vehicle.kind()","incorrect":"process()\n        -> shape as String\n        <- rtn as String: \"Truck: \"","explanation":"The dispatcher entry takes Vehicle. A handler for String is outside the Vehicle hierarchy and can never be reached through dispatch. See ek9 -h E05210 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 should implement ONLY the bridge trait, not the individual traits directly. If Duck lists the bridge AND the individual traits, whether the bridge handler still wins depends on the order they are listed in: some orders leave all three handlers tied at the same cost and the dispatch is ambiguous. Implementing only the bridge trait is the order-independent form.\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":"#!ek9\ndefines 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 the bridge handler cost 0.10, the individual trait handlers 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    //The dispatcher has a handler for each trait and for the bridge\n    Describer\n      describe() as dispatcher\n        -> thing as Any\n        <- rtn as String: \"thing\"\n\n      describe()\n        -> thing as Flyable\n        <- rtn as String: \"flyable\"\n\n      describe()\n        -> thing as Swimmable\n        <- rtn as String: \"swimmable\"\n\n      describe()\n        -> thing as AmphibiousFlyer\n        <- rtn as String: \"bridge\"\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()\n\n      describer <- Describer()\n      require describer.describe(duck) == \"bridge\"\n      require describer.describe(eagle) == \"flyable\"\n      require describer.describe(fish) == \"swimmable\"","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":["E05230","dispatcher ambiguity diamond"],"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":"with trait of AmphibiousFlyer\n      override canFly()","incorrect":"with trait of AmphibiousFlyer\n      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":"#!ek9\ndefines 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":["E05260","sealed dispatcher","exhaustive dispatcher handlers"],"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":"E05210","correct":"process()\n        -> cmd as CreateCommand\n        <- rtn as String: \"Processing create: \" + cmd.execute()","incorrect":"process()\n        -> cmd as String\n        <- rtn as String: \"Processing create: \" + cmd","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 E05210 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    ->\n      s1 as Shape\n      s2 as Shape\n    require s1? and s2?\n  intersect()\n    ->\n      s1 as Circle\n      s2 as Circle\n    require s1? and s2?\n  intersect()\n    ->\n      s1 as Circle\n      s2 as Rectangle\n    require s1? and s2?\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":["dispatcher resolution hierarchy","dispatch cost fallback","dispatcher entry resolution"],"typicalErrors":[{"error":"E05210","correct":"process()\n        -> entity as Person\n        <- rtn as String: \"Person: \" + entity.kind()","incorrect":"process()\n        -> entity as String\n        <- rtn as String: \"Person: \" + entity","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 E05210 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, in both directions. A pure dispatcher requires all handlers to be pure; a dispatcher that is not pure requires that no handler is pure. Callers only see the entry point, so its purity is the promise made about every handler.\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":"#!ek9\ndefines 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: handlers match the entry, so all are 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":"E05210","correct":"run() as pure\n        -> action as SaveAction\n        <- rtn as String: \"Running save: \" + action.perform()","incorrect":"run() as pure\n        -> action as String\n        <- rtn as String: \"Running save: \" + action","explanation":"The dispatcher entry takes Action. All handler parameter types must be subtypes of Action. String is outside the Action hierarchy. See ek9 -h E05210 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":"#!ek9\ndefines 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":["source formatting","code formatting","auto-formatting","indentation"],"typicalErrors":[{"error":"E50001","correct":"greeting <- greet(\"World\")\n      stdout.println(greeting)","incorrect":"greet(\"World\")\n      stdout.println(greeting)","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":"#!ek9\ndefines 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":["format all files","format project","bulk format"],"typicalErrors":[{"error":"E50001","correct":"add(3, 4)","incorrect":"adds(3, 4)","explanation":"Calling a function that is not defined (here the misspelt adds) leaves the call 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) on a file that does not parse because of a slip with exactly one reading. Use -fF to force-format all project files.\n\nFORCE FORMAT SINGLE FILE\n  ek9 -ff myfile.ek9\nForce format first repairs, as text, what stops a file parsing when only one reading of it is possible, then applies the normal formatter:\n- layout: a byte-order mark is removed, CRLF line endings become LF, a missing or wrong first line becomes #!ek9, tabs used for layout become spaces (a tab inside a String is content and is kept), an odd base indent width becomes 2 spaces per level;\n- slips from other languages: '-> a as Integer, b as String' becomes one parameter per line under a bare '->'; 'add(name as String)' on a declaration moves the parameter under '->'; a 'defines' block at column 0 moves to column 2; a bare 'default operator' moves to the end of its body; a stray trailing bracket is dropped; '&&' and '||' become 'and' and 'or'; a trailing ':' after an if/while condition is dropped; '#' and C block comments become EK9 comments; 'List<String>()' becomes 'List() of String'; 'Main() @Test' puts '@Test' on the line above; 'Type?: value' drops the '?'.\nEvery repair is reported, and the file is rewritten only if the result parses; otherwise it is left exactly as it was. Force format never renames anything: a misspelt keyword is left for you to correct, because guessing would rename valid identifiers (heads is a perfectly good name, not a typo for head).\n\nFORCE FORMAT ALL FILES\n  ek9 -fF myproject.ek9\nThis force-formats every .ek9 file in the project.\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 (repairs first).\n  ek9 -fF myproject.ek9  Force format all project files (repairs first).\n\nWHEN TO USE FORCE FORMAT\n- When an editor has put tabs into the indentation, or saved Windows line endings or a byte-order mark.\n- When code was written with another language's habits (comma parameters, '&&', a Python ':').\nWhen a compile fails for one of these reasons, the compiler's own hint suggests ek9 -ff, lists the repairs it would make - and only then.\n\nLIMITATIONS\nForce format does not guess: a slip with two readings is named and left for you - '%' (mod or rem?), 'assert x, \"message\"' (EK9's assert takes no message), '$name' inside a backtick String ('${name}' or a literal '\\$'), a misspelt keyword, or how many lines belong under an 'if'. The file is then left unchanged; fix it from the message and compile again.\n\nREPAIR COMPILE\n  ek9 -fc myfile.ek9\nForce format repairs only what stops a file parsing. ek9 -fc compiles the file in its project and applies every repair the compile errors give exactly one reading - the same parse repairs first, then, for example, a trait listed again that the super class already has (E02090) - compiling again until the file is clean or nothing more applies. It writes the repaired file, reports every repair, then reports what is still wrong as a compile does. It does not format.\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":"#!ek9\ndefines 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 repairs what stops a file parsing when it has exactly one reading (layout, comma parameters, '&&', a stray bracket ...), reports each repair, and never renames anything.","keywords":["broken","error","fF","ff","fix","force","format","indent","recover","style","syntax","unparseable"],"primaryTopics":["force format","format unparseable","format broken file"],"typicalErrors":[{"error":"E50001","correct":"cleaned <- sanitizeInput(userInput)\n      hasContent <- isNonEmpty(cleaned)","incorrect":"sanitizeInput(userInput)\n      hasContent <- isNonEmpty(cleaned)","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":"#!ek9\ndefines 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":["canonical formatting","gofmt style"],"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":"#!ek9\ndefines 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":["formatting workflow","format pre-commit","format CI verify"],"typicalErrors":[{"error":"E50001","correct":"ageIsValid <- validateAge(25)","incorrect":"ageIsValid <- validateAges(25)","explanation":"Calling 'validateAges' when only 'validateAge' is declared leaves the function name 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. Profiling is reported with the coverage results ('p' implies -tC), so the coverage summary is followed by the top functions and methods by self-time with their call counts.\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   Writes .ek9/test-results.json and .ek9/coverage.json; the profiling JSON is in coverage.json (for AI and tools).\n  ek9 -t3p myproject.ek9   Writes .ek9/test-results.xml and .ek9/coverage.xml; the XML carries no profiling data.\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":"#!ek9\ndefines 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":["profile program","profiling","performance profiling"],"typicalErrors":[{"error":"E50001","correct":"result <- factorial(tenFactorial)\n      stdout.println(`10! = ${result}`)","incorrect":"factorial(tenFactorial)\n      stdout.println(`10! = ${result}`)","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. Profiling is reported with the coverage results ('p' implies -tC): after the coverage summary, the human-readable output lists the top functions by self-time with their call counts.\n\nJSON OUTPUT FOR ANALYSIS\n  ek9 -t2p myproject.ek9\nWrites .ek9/test-results.json and .ek9/coverage.json; the 'profiling' section of coverage.json holds the data per function (calls, total, self, average, min, max, p50/p95/p99). 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":"#!ek9\ndefines 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":["profile tests","t2p t6p"],"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 HTML report whose flame graph is a static SVG. 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\nHOVER TOOLTIPS\nThe flame graph is a static SVG: it does not zoom, search or reset. Hover over a frame to see its total time and self time. Call counts are not in the tooltip - read them from the hot function table on the same page.\n\nACTIONABLE STRATEGY\n1. Look for the widest red frames. These are your bottlenecks.\n2. Check call count in the hot function table. 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. Follow the stack upwards to 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":"#!ek9\ndefines 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, static SVG with hover tooltips, 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":["flame graph","read flame graph","interpret flame graph"],"typicalErrors":[{"error":"E50001","correct":"transformed <- transformItem(item)\n        total: total + transformed","incorrect":"transformItem(item)\n        total: total + transformed","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 the 'profiling' section that -t2p writes to .ek9/coverage.json both carry, per function:\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\nThe -tp human-readable output is a short summary after the coverage summary: the top functions by self-time with their call counts.\n\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 profiling JSON in .ek9/coverage.json 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":"#!ek9\ndefines 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 summary in -tp (text) and a full table in -t6p (HTML), sorted by self-time, with p95/p99 percentiles in the HTML and the -t2p JSON 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 'profiling' section of .ek9/coverage.json 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 -O0 myproject.ek9 (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":"#!ek9\ndefines 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":"iterativeElapsed <- afterIterative - beforeIterative","incorrect":"iterativeElapsed <- afterIterativeXYZ - beforeIterative","explanation":"A misspelt variable (afterIterativeXYZ) at its use does not resolve, 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?","Why must variables be declared before use in EK9?","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 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 follow strict sequential order. (A property read inside a property initialiser before its own initialiser has run is E08060, not E08010 - see Q1397.)\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":"#!ek9\ndefines 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","declare","define","defined","forward","hoisting","initialize","migrate","order","reference","safety","scope","use","variable"],"primaryTopics":["define before use","variable initialization","declaration order","E08010","data flow safety"],"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)\n      stdout.println(`Total: ${totalPrice}`)","incorrect":"computeTotal(100.0)\n      stdout.println(`Total: ${totalPrice}`)","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":"#!ek9\ndefines 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":"E08020","correct":"      label <- \"Amount\"","incorrect":"      label as String?\n      if showDecimals\n        label :=? \"Amount\"","explanation":"'label' is declared without a value and is only assigned inside the if, so it might be used before being initialised on the other path. Give it a value at declaration, or set it on every path before it is read. See ek9 -h E08020 for details."}],"companions":[]}
{"id":634,"category":"Data Flow Safety","question":"Why must I use a guard before accessing a returned value?","url":"https://ek9.io/qa/QA0634.html","alternatePhrasings":["How do guards prevent unsafe access?","What happens if I skip the guard on a function return?","What is E08030 unsafe method access?"],"answer":"EK9 declaration guards let you act only on a value that is really set. Without the guard, the value might be unset, and the code would run on a missing value.\n\nDECLARATION GUARD 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). EK9 has no null, so calling a method on an unset String or Integer does not crash: the result is simply unset too (tri-state propagation), and the guard is how you run code only when there is a real value. For Optional, Result and Iterator the compiler goes further: get-style access to the contents without a guard or isSet check is rejected at compile time (E08030).\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":"#!ek9\ndefines 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(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 declaration guards, 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: declaration guard '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 declaration guard 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\nALSO FORBIDDEN: REASSIGNMENT\nReassigning an existing local variable with : (or :=) inside a pure scope is rejected as E08100. Reassigning a parameter is E08110, in any scope.\n\nALLOWED IN PURE CONTEXT\nDeclaring a fresh variable (x <- expr) creates a new binding and is allowed. Assigning the return variable is allowed. Assign-if-unset (:=?) is allowed. 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\nNEW BINDINGS VS MUTATION\nMutation (variable += delta) modifies the existing object in place: E08120. Reassignment (variable: newValue) is also refused in a pure scope: E08100. Write the computation as fresh declarations (next <- current + delta) or as the initialiser of the return variable, as projectedBalance does.\n\nSee Q560 for pure method basics. See Q566 for pure call chain restrictions. See Q273 for purity as a security boundary.","ek9Example":"#!ek9\ndefines 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 the + operator (non-mutating) in the return variable's initialiser;\n        no variable is reassigned.\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 and E08100 rejects reassignment 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())\n\n      //Pure call: safe, no mutation","incorrect":"      stdout.println(acct.formatSummary().toUpperCase())\n\n      //Pure call: safe, no mutation","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E08120","correct":"        <- projected as Float: balance + deposit","incorrect":"        <- projected as Float: balance + deposit\n        balance += deposit","explanation":"A pure method may not use a mutation operator: 'balance += deposit' would change the account's state. Compute the new value with the non-mutating + instead. See ek9 -h E08120 for details."}],"companions":[]}
{"id":636,"category":"Data Flow Safety","question":"How do I chain multiple guards for safe access?","url":"https://ek9.io/qa/QA0636.html","alternatePhrasings":["Can I nest guards 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 declaration guards by nesting if-guards or using sequential guards with explicit isSet checks.\n\nNESTED GUARDS\nNest declaration guards 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 declaration guards 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, a lookup that returned nothing would flow on as an unset value into the computation (for Optional and Result values, unguarded get-style access is the compile-time error E08030).\n\nSee Q634 for guard basics. See Q163 for Optional unwrapping. See Q47 for Optional type patterns.","ek9Example":"#!ek9\ndefines 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 declaration guards '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 declaration guard 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  isLower <- currentPrice < previousPrice\n  isSame <- scoreA == scoreB\n  order <- 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":"#!ek9\ndefines 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    stdout.println(age)\n  if temperature > BOILING_POINT\n    stdout.println(temperature)\n  if count == EXPECTED_COUNT\n    stdout.println(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 E11064: magic literals in comparisons.\n\nSee Q637 for self-comparison detection. See Q558 for tautological conditions. See Q639 for Boolean comparison patterns.","ek9Example":"#!ek9\ndefines 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, E11064 for magic literals.","keywords":["E08082","E11064","comparison","constant","dead","literal","magic","migrate","named","number","pattern","value"],"primaryTopics":["named constants","magic literals","E08082"],"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 E08084 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 E08084. 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":"#!ek9\ndefines module qa.comparison.booleanuse\n\n  defines function\n\n    <?-\n      Correct: Boolean value used directly in condition.\n      Adding '== true' would trigger E08084.\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 E08084.\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 E08084 for comparing Boolean to literal.","keywords":["E08084","boolean","comparison","condition","direct","false","literal","pattern","redundant","true"],"primaryTopics":["redundant boolean comparison","E08084","compare boolean true"],"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). A List returned from a function is set too: every route to a List goes through a constructor, so a function cannot hand back an unset List. The compiler does not flag a guard on a function's result (it does not look inside the function), but that guard always passes. An isSet check is genuinely needed only when the value can really be missing, and the way to say 'there may be no list at all' is to wrap it, for example in an Optional.\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\nNO RESULTS: TEST WITH EMPTY\nA search that finds nothing returns an empty List, which is SET. Test it with 'empty':\n  noResults <- searchByKeyword(\"unknown\")\n  if noResults empty\n    stdout.println(\"No results\")\nA guard such as 'if noResults <- searchByKeyword(\"unknown\")' would ENTER its body here, because the empty List is set.\n\nWHEN ISSET IS NEEDED\nWhen there may be no collection at all, return an Optional of the collection and guard on that:\n  if scores <- lookupScores(studentId)\n    stdout.println(`Found ${length scores.get()} scores`)\nAn Optional with nothing in it is unset, so the guard skips the body.\n\nEMPTY VS UNSET\nAn empty list (0 items) is SET. It exists, it is valid, it just has no elements. An unset value has no meaningful value yet. These are different states. Use 'empty' to check for no elements; use '?' (or a guard) on something that can really be unset, such as an Optional.\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":"#!ek9\ndefines module qa.comparison.collectionisset\n\n  defines function\n\n    <?-\n      Simulates a search. It always returns a SET List: empty when nothing matches.\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. Like every List, the result is set; it is empty for an unknown student.\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      A lookup where there may be NO list at all: the Optional is unset for an unknown student.\n    -?>\n    lookupScores()\n      -> studentId as Integer\n      <- rtn as Optional of List of Integer: Optional() of List of Integer\n\n      found <- findScores(studentId)\n      if found not empty\n        rtn: Optional(found)\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      //A constructed collection is set, with or without items: test it with 'empty', never '?'\n      fresh <- List() of String\n      for title in searchByKeyword(\"rust\")\n        fresh += title\n      if fresh empty\n        stdout.println(\"Nothing collected, but the List is still set\")\n\n      //A List returned from a function is set too, so this guard always enters\n      if results <- searchByKeyword(\"ek9\")\n        stdout.println(`Found ${length results} results`)\n        for item in results\n          stdout.println(`  ${item}`)\n\n      //No results: the List is SET but empty, so test it with 'empty', not '?'\n      noResults <- searchByKeyword(\"unknown\")\n      if noResults empty\n        stdout.println(\"No results for 'unknown'\")\n\n      //Genuinely needed: an Optional that may hold no List at all\n      if scores <- lookupScores(1)\n        above80 <- countAboveThreshold(scores.get(), 80)\n        stdout.println(`Scores above 80: ${above80}`)\n\n      missing <- lookupScores(2)\n      stdout.println(`Scores held for student 2: ${missing?}`)\n\n      //Empty check on a returned List\n      for studentId in [1, 2]\n        knownScores <- findScores(studentId)\n        if knownScores empty\n          stdout.println(`Empty scores for student ${studentId}`)\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 (a List returned from a function included), E08088 for redundant isSet check, 'empty' for no elements, Optional of List when there may be no list at all.","keywords":["E08088","E08089","check","collection","compare","comparison","constructed","dict","empty","isSet","list","pattern","redundant"],"primaryTopics":[],"typicalErrors":[{"error":"E08088","correct":"fresh <- List() of String\n      for title in searchByKeyword(\"rust\")\n        fresh += title\n      if fresh empty","incorrect":"fresh <- List() of String\n      if fresh?","explanation":"A collection made by its constructor is always set, so checking it with ? is always true and EK9 rejects the check. Test for no elements with 'empty'. See ek9 -h E08088 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 call with a return value is used 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 OPERATOR RESULT (E11050)\nAn operator that returns a value, invoked in method-call form as a standalone statement, is rejected: 'a.+(b)' computes a sum and throws it away. (An ordinary non-pure function call such as 'fetchAndLog(42)' compiles as a bare statement - its side effects may be the point - but capturing the result is still the habit to keep.)\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":"#!ek9\ndefines 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      Discarding the result compiles (it is not pure), but capturing it is still good practice.\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 E11051 for discarding the result of a pure function, E11050 for a discarded operator result, E11055 for a discarded constructor, E11052 for a discarded Result or Optional.","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":"E11051","correct":"total <- computeSum(10, 20)","incorrect":"total <- computeSum(10, 20)\n      computeSum(1, 2)","explanation":"computeSum is a pure function, so the extra bare call computeSum(1, 2) discards its result and achieves nothing. Capture the result. See ek9 -h E11051 for details."},{"error":"E11051","correct":"priceStr <- formatPrice(9.99)","incorrect":"priceStr <- formatPrice(9.99)\n      formatPrice(1.5)","explanation":"formatPrice is a pure function, so the extra bare call formatPrice(1.5) discards its result and achieves nothing. Capture the result. See ek9 -h E11051 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    first as A?\n    second as B?\n    Pair() as pure\n      first :=? A()\n      second :=? B()\n    Pair() as pure\n      ->\n        a as A\n        b as B\n      first :=? a\n      second :=? b\n    default operator ?\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":"#!ek9\ndefines 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() as pure\n        -> initialValue as T","incorrect":"      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":"#!ek9\ndefines 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 take A first and B second:\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\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    wrapped as T?\n\n    Position() as pure\n      wrapped :=? T()\n\n    Position() as pure\n      -> x as T  // T matches the type parameter\n      wrapped :=? x\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 Q1286 for public constructor requirement.","ek9Example":"#!ek9\ndefines 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() as pure\n        ->\n          a as A\n          b as B\n        first :=? a\n        second :=? b","incorrect":"      Entry() as pure\n        ->\n          a as B\n          b as A\n        first :=? a\n        second :=? b","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":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 at all (E06070). Every local declared with '<-' needs an explicit type, even when the value does not depend on T (for example 'counter as Integer: 0', not 'counter <- 0').\n\nINFERENCE LIMITATION\nInside a generic definition the compiler rejects every '<-' declaration, whether or not the type involves T. Declare locals with an explicit type: 'counter as Integer: 0', 'copy as T: T(item)'.\n\nEXPLICIT RETURN TYPES\nGeneric functions must declare return types explicitly:\n  defines function\n    transform() of type (S, T) as open\n      -> source as S\n      <- target as T: source? <- T() else T()\n\nFIELD DECLARATIONS\nFields in generic classes must use the type parameter explicitly:\n  Container of type T\n    item as T: 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":"#!ek9\ndefines 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?\n      require source?\n      target: T()\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 (E06070), every local needs an explicit type.","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  defines function\n    CheckFunction()\n      -> text as String\n      <- rtn as Boolean: text?\n    describeItem() of type T constrain by CheckFunction as open  // ERROR E06080\n      -> item as T\n      <- rtn as String: $item\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  defines function\n    Mapper() as abstract\n      -> inputText as String\n      <- rtn as String?\n    process()\n      -> mapper as Mapper\n      <- rtn as String: mapper(\"input\")\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":"#!ek9\ndefines 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      -> text as String\n      <- rtn as Boolean: text?\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":["function constraint","E06080","constrain by function"],"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 as EventHandler: () with trait of EventHandler as class\n    override handle()\n      <- rtn as Boolean: true\nNo name means no monomorphisation conflict. Note the explicit type on the variable: inside a generic body '<-' inference is rejected (E06070), so declare it 'handler as EventHandler: ...'.\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":"#!ek9\ndefines 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":"A generic function that is not abstract must provide an implementation (a bodiless one is E07110, which asks you to declare it 'abstract' instead). A generic function declared 'as abstract' may omit its body, but then every dynamic function built from it must supply one (E06100).\n\nIMPLEMENTATION REQUIRED\nA non-abstract generic function must have a body:\n  defines function\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  defines function\n    compare() of type T as open\n      ->\n        a as T\n        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":"#!ek9\ndefines 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?\n      require second?\n      rtn: A(first)\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: a non-abstract generic function needs a body (E07110); an abstract generic function may omit it, and a dynamic function created from an abstract generic function must supply the body (E06100).","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":"A generic function declared 'as open' must provide an implementation body, because a template with a body is what gets instantiated for each type. A bodiless generic function must be declared 'abstract' instead (E07110), and then a dynamic function created from it must supply the body (E06100). 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":"#!ek9\ndefines 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":"E06120","correct":"isPresent() as pure\n        <- rtn as Boolean: stored?","incorrect":"combine() as pure\n        -> other as T\n        <- rtn as T: stored + other\n\n      isPresent() as pure\n        <- rtn as Boolean: stored?","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, so a generic that uses '+' on a T-typed value is rejected as soon as it is parameterised with a function. Functions are called, not manipulated with arithmetic or mutation operators. See ek9 -h E06120 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  defines class\n    Shape as abstract\n      shapeName as String: String()\n      Shape()\n        -> n as String\n        shapeName: n\n\n      default operator ?\n    Circle extends Shape\n      Circle()  // matches Shape's constructor\n        -> n as String\n        super(n)\n\nSee Q195 for constrain by basics. See Q642 for constructor inference. See Q647 for function constraint limits.","ek9Example":"#!ek9\ndefines 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":"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  okResult <- Result() of (Integer, String)   // correct: Integer ok, String error\n  badResult <- 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 the ok value and the failure value:\n  userResult <- Result() of (UserRecord, String)     // success: record, error: message\n  countResult <- Result() of (Integer, ErrorCode)    // success: count, error: enum\n  valueResult <- Result() of (Float, Exception)      // success: value, error: exception\n\nCREATING RESULTS\nUse the ok or the failure 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":"#!ek9\ndefines 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  circles <- List() of Circle    // one type\n  shapes <- List() of Shape      // completely different type\n  numbers <- 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  Processor\n    process()\n      -> circles as List of Circle\n      <- rtn as Integer: length circles\n\n    process()\n      -> shapes as List of Shape\n      <- rtn as Integer: length shapes\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":"#!ek9\ndefines module qa.genericsdeep.typeindependence\n\n  defines class\n\n    Hue as abstract\n      colourName as String: String()\n\n      default Hue() as pure\n\n      Hue() 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 Hue\n      default Red() as pure\n      Red() as pure\n        -> n as String\n        super(n)\n\n    Blue is Hue\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  names <- List() of String         // correct: 1 param, 1 arg\n  maybeCount <- Optional() of Integer    // correct: 1 param, 1 arg\n\nMULTI PARAMETER TYPES\nTypes declared 'of type (K, V)' need exactly two:\n  lookup <- 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":"#!ek9\ndefines 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 (String, Float)","incorrect":"Association() of String","explanation":"The number of type arguments at a USE must match the number of type parameters declared on the generic. Association declares two parameters (K, V), so supplying only one - Association() of String - triggers E06020. (Changing the declaration itself to 'Association of type K' is a different error, E06030: the generic would then declare one parameter while its constructor still takes two.) 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 (METHODS)\nBecause List of Integer and List of String are distinct types, a class can declare both:\n  Counter\n    count()\n      -> items as List of Integer\n      <- rtn as Integer: length items\n    count()\n      -> items as List of String\n      <- rtn as Integer: length items\nBoth signatures coexist without ambiguity.\n\nFUNCTIONS CANNOT BE OVERLOADED\nModule level functions cannot share a name whatever their parameters - two functions called process is E01030 (duplicate name). Give functions distinct names (sumIntegers, joinStrings).\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 methods with different type arguments is always unambiguous: a class may declare handler(opt as Optional of String) and handler(opt as Optional of Integer) side by side.\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":"#!ek9\ndefines module qa.genericsdeep.methodoverloading\n\n  defines function\n\n    <?-\n      Module level FUNCTIONS cannot be overloaded at all (two functions with the same name are E01030\n      whatever their parameters), so these have different names. Overloading by parameterised type\n      is done with METHODS on a class - see Counter below.\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\n  defines class\n\n    <?-\n      Methods accepting different parameterised types.\n      Each List parameterisation is an independent type,\n      so these are unambiguous overloads (unlike Java with erasure).\n    -?>\n    Counter\n\n      count() as pure\n        -> items as List of Integer\n        <- rtn as Integer: length items\n\n      count() 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      // === OVERLOADED METHODS FOR DIFFERENT LIST TYPES ===\n\n      counter <- Counter()\n      intCount <- counter.count(numbers)\n      stdout.println(`Integer count: ${intCount}`)\n\n      strCount <- counter.count(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":["overload parameterised","E06140"],"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 a duplicate-name error: module level functions cannot be overloaded, so a second function with the same name is rejected whatever its parameter types. Give each function its own name, or use methods on a class when you want overloads by parameterised type. 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 error do I get for too many or too few arguments in a function call?","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, method or constructor matches the declared parameter count. The error code depends on what is being called: a plain function gives E06270, a generic function gives E06020, and a method or constructor gives E50060.\n\nEXACT MATCH REQUIRED\nA function declared with two parameters:\n  combine()\n    ->\n      a as String\n      b as String\n    <- rtn as String: a + b\nMust be called with exactly two arguments:\n  combine(\"hello\", \"world\")  // correct\n\nTOO MANY OR TOO FEW (FUNCTION, E06270)\nPassing the wrong number of arguments to a non-generic function is a parameter mismatch:\nCalling combine(\"a\", \"b\", \"c\") or combine(\"a\") is E06270: the supplied arguments are not appropriate (parameter mismatch).\n\nGENERIC FUNCTION CALLS (E06020)\nA generic function whose type is inferred from the call is checked the same way, with its own code:\nIf transform takes exactly one argument (source as S), then calling transform(x, y) is E06020: type/function is generic, but incorrect number of parameters supplied.\n\nMETHODS AND CONSTRUCTORS (E50060)\nCalling a method or constructor with the wrong number of arguments reports that the method could not be resolved:\nIf add takes two arguments, calling calc.add(1) is E50060: parameter mismatch, method/function not resolved.\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":"#!ek9\ndefines 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 (E06270 for functions, E06020 for generic functions, E50060 for methods and constructors).","keywords":["E06020","E06270","E50060","argument","count","function","generic","too few","too many","type-parameter","validation"],"primaryTopics":["E06270","E06020","E50060"],"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: under 'defines service' the service name is followed by its base path, as in 'Items for :/items'. This binds the service to the '/items' path prefix. The 'for' before every URI is required, as is the 'as' before an HTTP verb ('welcome() as GET for :/welcome'): leaving either out is E07671, and the editing tools insert it for you.\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 -= for :/{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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`[\"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 Bytes: Bytes().withUtf8(`{\"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 for :/items' on service, 'method() as GET for :/welcome' on method.","keywords":["E07671","E07790","HTTP","REST","dict","endpoint","http","mapping","path","route","service","uri"],"primaryTopics":["service uri mapping","service endpoint route"],"typicalErrors":[{"error":"E07671","correct":"    Catalogue for :/catalogue open","incorrect":"    Catalogue :/catalogue open","explanation":"A service URI is written after 'for' - the service's own and each method's. Without it the compiler reports E07671 at the URI. See ek9 -h E07671 for details."},{"error":"E07671","correct":"      listAll() as GET for :/all","incorrect":"      listAll() GET for :/all","explanation":"An HTTP verb is written after 'as'. 'listAll() GET' is E07671 at the verb; 'listAll() as GET for :/all' is the one form. See ek9 -h E07671 for details."},{"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\n    <- response as HTTPResponse: ...\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 Bytes: Bytes().withUtf8(`{\"id\": \"${capturedId}\"}`)\nThe captured variable is accessible within the dynamic class methods.\n\nCONTENT BINDING\nUse :=: CONTENT for request body:\n  operator += for :/\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":"#!ek9\ndefines module qa.webdeep.pathbinding\n\n  defines service\n\n    <?-\n      Service demonstrating path parameter extraction\n      and content body binding.\n    -?>\n    Orders for :/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 Bytes: Bytes().withUtf8(`{\"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 += for :/\n        -> orderJson as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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":["E07880","service path binding"],"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?","Why must all EK9 service methods return HTTPResponse?","What triggers E07800 SERVICE_MISSING_RETURN?","Why can't my service method omit a return type?"],"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 Bytes\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 Bytes: Bytes().withUtf8()\"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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"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 Bytes: Bytes().withUtf8(`{\"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","endpoint","function","http","required","return","service","status","trait"],"primaryTopics":["HTTPResponse return","E07800","service method return","service return type","SERVICE_MISSING_RETURN"],"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 += for :/           POST (create resource)\n  operator -= for :/{id}       DELETE (remove resource)\n  operator :~: for :/{id}      PATCH (partial update)\n  operator :^: for :/{id}      PUT (full replacement)\n\nPATH PARAMETERS IN OPERATORS\nOperators can include path parameters:\n  operator -= for :/{resourceId}\n    -> resourceId as String\n    <- response as HTTPResponse: ...\n\nCONTENT BINDING IN OPERATORS\nPOST and PUT operators accept request body:\n  operator += for :/\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":"#!ek9\ndefines 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 for :/products open\n\n      // GET /products — list\n      listProducts() for :/\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`[\"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 += for :/\n        -> productJson as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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 -= for :/{productId}\n        -> productId as String\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(\"\")\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":["crud uri mapping","service crud"],"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. The receiving function or method cleans each such parameter on entry, replacing it with its own defensive copy (or an unset String for a threat).\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\nThe cleaning is generated at the ENTRY of the function or method that receives the parameter. Fields are not received (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\n    <- result as String: userInput\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":"#!ek9\ndefines module qa.sanitizeddeep.paramonly\n\n  defines function\n\n    <?-\n      Sanitized on incoming String parameter.\n      The function cleans the parameter on entry: it replaces it\n      with its own 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, cleaned automatically on entry to the receiver.","keywords":["E07910","E07920","String","injection","parameter","receiver","sanitize","sanitized","security","validate"],"primaryTopics":["E07910","E07920"],"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()\n    -> input as sanitized String\n    alias <- input       // ERROR E07930: 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":"#!ek9\ndefines 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":["sanitized aliasing","E07930","sanitized defensive copy"],"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":"Can an override add or drop 'sanitized' on a parameter?","url":"https://ek9.io/qa/QA0663.html","alternatePhrasings":["What is E07940 sanitized dropped in an override?","Can I add sanitized to an override method?","What happens if I remove sanitized from an overriding method?"],"answer":"'sanitized' is part of the contract of a method or function, like 'pure'. An override may ADD it to a parameter, but may never DROP it (E07940). This holds for class methods, trait methods and functions that are 'is' a super function.\n\nWHY DROPPING IS REJECTED\nConsider polymorphic dispatch:\n  handler <- getHandler()      // Could be Base or Derived\n  handler.process(userInput)   // Base promised to clean userInput\nIf Derived dropped 'sanitized', untrusted input would reach code that never cleans it whenever the runtime type is Derived.\n\nWHY ADDING IS ALLOWED\nThe receiving method or function cleans its own 'sanitized' parameters, so an override that adds 'sanitized' cleans its input on every route - including a call through the base type. A subtype may weaken a precondition, never strengthen it (Liskov Substitution Principle).\n\nCORRECT PATTERNS\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 keep it\n\n  plainFormatter() as abstract\n    -> text as String\n  strictFormatter() is plainFormatter\n    -> text as sanitized String       // may add it\n\nEVERY PARENT COUNTS\nA method that implements a trait AND overrides a super (or implements two traits) must keep 'sanitized' if ANY of them declares it.\n\nDISPATCHERS\nEvery dispatcher handler must keep the 'sanitized' parameters of its dispatcher entry point: a call made through the entry point is routed to the handler before the entry point's own body runs, so the handler must clean the value itself.\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":"#!ek9\ndefines module qa.sanitizeddeep.overridematch\n\n  defines function\n\n    <?-\n      Abstract function whose parameter is sanitized.\n      Every implementation must keep it sanitized.\n    -?>\n    cleanFormatter() as abstract\n      -> text as sanitized String\n      <- result as String?\n\n    <?-\n      Keeps the sanitized parameter of its super function.\n    -?>\n    bracketed() is cleanFormatter\n      -> incoming as sanitized String\n      <- result as String: \"\"\n\n      safeCopy <- String(incoming)\n      result: `[${safeCopy}]`\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 one sanitized parameter and one plain parameter.\n      Subclasses MUST keep sanitized where the base has it, and MAY add it.\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      describe()\n        -> label as String\n        <- summary as String: \"\"\n\n        summary: `Base describes: ${label}`\n\n      default operator ?\n\n    <?-\n      Keeps sanitized on handleRequest, and ADDS it on describe.\n      Security behavior is at least as strict as the parent's.\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      override describe()\n        -> label as sanitized String\n        <- summary as String: \"\"\n\n        safeCopy <- String(label)\n        summary: `Derived describes: ${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)\n\n      asBase as BaseHandler: derivedHandler\n      stdout.println(asBase.describe(\"through the base\"))\n\n      stdout.println(bracketed(\"formatted\"))","migrationContext":"Java: no enforcement, override can change validation. Python: no enforcement. Go: no inheritance. Rust: trait implementations must match exactly. EK9: the compiler rejects an override that drops sanitized (E07940) and allows one that adds it, for methods and functions alike.","keywords":["E07940","LSP","Liskov","abstract","function","handler","match","open","override","polymorphic","sanitize","sanitized","sealed","security","validate","virtual","visitor"],"primaryTopics":["E07940","sanitized liskov"],"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."},{"error":"E07940","correct":"-> incoming as sanitized String","incorrect":"-> incoming as String","explanation":"The super function cleanFormatter() marks its parameter 'sanitized', so an implementation may not drop it - a caller holding cleanFormatter expects the input to be cleaned. See ek9 -h E07940 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 gets the OLD value too (the surprise is that the increment happens AFTER the read)\nBoth are sources of subtle bugs, and x++ versus ++x is a classic way to write the wrong one.\n\nCORRECT PATTERN\n  x <- 0\n  x++                    // Statement only: increments x\n  y <- x                 // Assign separately if needed\n  // Or use explicit arithmetic:\n  z <- 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":"#!ek9\ndefines 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":["++ operator","-- operator","E07950"],"typicalErrors":[{"error":"E07950","correct":"counter--","incorrect":"decremented <- 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 'decremented <- counter - 1' instead. See ek9 -h E07950 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 += for :/\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\n    <- response as HTTPResponse: ...\nPath parameters from URLs can also be sanitized.\n\nPATTERN: SAFE API\nCombine sanitized with copy constructor in service methods:\n  operator += for :/\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":"#!ek9\ndefines 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 for :/api open\n\n      // GET /api/status — no sanitization needed for read\n      status() for :/status\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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 += for :/\n        -> payload as sanitized String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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 sanitization, at the HTTP boundary and again on entry to the operation.","keywords":["CONTENT","E07910","E07930","binding","sanitize","sanitized","security","service","validate","web"],"primaryTopics":["sanitized content binding","sanitized service","web service sanitized"],"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":"#!ek9\ndefines 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\nInjecting the concrete type instead is wrong and fails with E08150:\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 ConsoleLogger!       // ERROR: E08150\n      logger.log(\"hello\")\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":"#!ek9\ndefines 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":"Injection - a variable declared with the '!' suffix - can be written in components, services, classes, traits, functions, dynamic classes and dynamic functions, as a property or inside a method or function body. A program can inject too, once it is linked to an application. A record cannot (E08160): it holds data only, and its fields take part in its generated equality, hashing and copying.\n\nWHAT THE COMPILER CHECKS\n- the injected type must be an abstract component (E08160 for any other genus, E08150 for a concrete component)\n- the variable must not be a parameter or a return value (E08160)\n- the scope must not be pure (E08140)\n- a program that injects must declare 'with application of AppName' (E08220)\n- that application must register every type the program injects (E08210)\n\nINSIDE A DYNAMIC CLASS\nThe injection belongs to the dynamic class, not to the class, service or program it is written in. The DI checks follow the calls to wherever the dynamic class is created; a dynamic class in a program's body is satisfied by that program's application.\n\nKEEP IT AT THE TOP\nInjection ties code to the application's wiring, so inject sparingly and as high up as possible.\n\nSee Q111 for component basics. See Q233 for injection in a dynamic class. See Q666 for pure context. See Q667 for abstract injection. See Q672 for program-application linking.","ek9Example":"#!ek9\ndefines 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: '!' can be written in any construct except a record, but only an abstract component can be injected, and a program must name the application that does the wiring.","keywords":["DI","E08160","component","constant","context","dynamic class","inject","injection","program","record","registration","service","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E08220","correct":"InjectableContextDemo() with application of OrderApp","incorrect":"InjectableContextDemo()","explanation":"A program that injects must name the application that does the wiring - without 'with application of', nothing says which concrete component to inject. See ek9 -h E08220 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":"An injected variable ('cache as CacheService!') is never assigned - not with ':=', and not with ':=?' either (E08170). The application declares which concrete component satisfies each abstract injection point; if code could reassign the variable, the wiring would become unpredictable.\n\nAN INJECTED COMPONENT IS ALWAYS SET\nThe compiler proves that every injection a program can reach is registered in its application (E08210, E08220), so the value always arrives. That is also why ':=?' (assign only if unset) would never apply, and why checking it - 'if cache?', 'require cache?', 'cache ?: other', 'x <- cache? <- cache else other' - is itself an error (E08266): the check is always true.\n\nTHE FALLBACK IDIOM: A NEW VARIABLE\nWhen something else may supply a component and the injected one is the default, the fallback runs the other way round - assign to a NEW variable, then fall back to the injected one:\n\n  chosen <- supplied\n  chosen :=? cache    // only if 'supplied' was unset\n\nThe example below passes a set and an unset component to such a function: the set one is used, the unset one falls back to the injected cache.\n\nSee Q667 for abstract injection. See Q668 for injectable contexts. See Q670 for field initialization.","ek9Example":"#!ek9\ndefines 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 - unset when it has no prefix.\n    -?>\n    InMemoryCache extends CacheService\n      prefix as String?\n\n      InMemoryCache()\n        prefix: String()\n\n      InMemoryCache()\n        -> keyPrefix as String\n        prefix: keyPrefix\n\n      override lookup()\n        -> cacheKey as String\n        <- cachedValue as String: `${prefix}${cacheKey}`\n\n      default operator ?\n\n  defines function\n\n    <?-\n      Use the supplied cache if it is set, else fall back to the injected one.\n      The injected variable is never assigned: the fallback goes in a NEW variable.\n    -?>\n    chooseCache()\n      -> supplied as CacheService\n      <- chosen as CacheService?\n      cache as CacheService!\n      chosen: supplied\n      chosen :=? cache\n\n  defines application\n\n    CacheApp\n      register InMemoryCache(\"cached:\") 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      preferred <- chooseCache(InMemoryCache(\"preferred:\"))\n      stdout.println(`Preferred: ${preferred.lookup(\"theme\")}`)\n\n      fallback <- chooseCache(InMemoryCache())\n      stdout.println(`Fallback: ${fallback.lookup(\"theme\")}`)","migrationContext":"Java: Spring injection is final by convention (not enforced), and optional injection (@Autowired(required = false)) can leave a null to check. Python: no enforcement. Go: manual wiring can be changed. Rust: ownership prevents aliasing. EK9: the compiler prevents any assignment to an injected variable, proves the injection always arrives, and rejects set-checks on it; a fallback goes in a new variable.","keywords":["DI","E08170","E08266","component","fallback","immutable","inject","injection","isset","null-safe","reassignment","registration","safe","validate"],"primaryTopics":["injected reassign","E08170"],"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 application and are never assigned, with ':=' or with ':=?'. The application wiring defines which implementation satisfies each injection point, and manual reassignment would break that contract. See ek9 -h E08170 for details."},{"error":"E08266","correct":"      cache as CacheService!\n      chosen: supplied\n      chosen :=? cache","incorrect":"      cache as CacheService!\n      chosen: cache? <- cache else supplied","explanation":"An injected component is always set, so 'cache?' is always true and the 'else' never runs. For a fallback, assign to a NEW variable and fall back to the injected one with ':=?'. See ek9 -h E08266 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      default operator ?\n\nFIELD INJECTION\n  defines component\n    MyComponent\n      repository as Repository!          // Injected by container\n      default operator ?\n\nBOTH IN SAME COMPONENT\n  defines component\n    OrderService\n      repository as Repository!          // Injected\n      retryLimit as Integer: 3           // Initialized\n      default operator ?\n\nSee Q667 for abstract injection. See Q669 for reassignment. See Q671 for circular dependencies.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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":["circular injection","E08190"],"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":"#!ek9\ndefines 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":["with application of","E08220"],"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":"#!ek9\ndefines 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":["unregistered component","E08210"],"typicalErrors":[{"error":"E08210","correct":"register TokenAuth() as AuthService\n      register InMemorySessionManager() as SessionManager","incorrect":"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 rejects the missing registration with E08210. See ek9 -h E08210 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":"#!ek9\ndefines 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":["duplicate registration","E08230","register twice"],"typicalErrors":[{"error":"E08230","correct":"ProductionConfig\n      register SmtpEmailService() as EmailService","incorrect":"ProductionConfig\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, and is rejected with E08230. Use separate applications for different wiring configurations. See ek9 -h E08230 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":"#!ek9\ndefines 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":"E08210","correct":"register DatabaseAccess() as DataAccess\n      register OrderLogic() as BusinessLogic","incorrect":"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 is rejected with E08210. See ek9 -h E08210 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 override required in a class hierarchy?","What is E05130 access modifier mismatch in override?","What is E05110 override takes other parameter types than the parent?","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 exactly the parent's, and the parameter types 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 keeps the parent's access modifier exactly. Narrowing (public to protected) and widening (protected to public) both trigger E05130 ('methods with same signature have different access modifiers'). 'override private' is E07010 - a private method is never an override.\n\nSIGNATURE MATCHING (E05110, E07440)\nParameter types must match exactly - different parameter types make a new overload, and 'override' on it is E05110. The return type may be narrowed to a subtype (a covariant return); an incompatible return type is E07440.\n\nTHREE-LEVEL CHAIN\nGrandparent declares the contract. Parent overrides it. Grandchild overrides it again. Each level must independently satisfy the 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":"#!ek9\ndefines 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 (protected to public), 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 modifier must match exactly (no widening, unlike Java), parameter types must match exactly, covariant returns allowed.","keywords":["E05110","E05120","E05130","abstract","access","chain","class","hierarchy","inherit","open","override","protected","signature","virtual"],"primaryTopics":["E05120","E05130"],"typicalErrors":[{"error":"E05120","correct":"override protected describe()\n        <- rtn as String: \"parent\"","incorrect":"protected describe()\n        <- rtn as String: \"parent\"","explanation":"Parent's describe() replaces Grandparent's, so it must say 'override'; without it the compiler reports E05120. (In a class that is NOT open, as Child is, the same omission is reported first as E07260 - a 'protected' method is only allowed in an open class.) See ek9 -h E05120 for details."},{"error":"E07010","correct":"override protected classify() as pure\n        <- rtn as String: \"child-class\"","incorrect":"override private classify() as pure\n        <- rtn as String: \"child-class\"","explanation":"A private method is never an override, so 'override private' is rejected with E07010. The override keeps the parent's access modifier exactly - protected here (any other modifier is E05130). 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 E05030 not open to be extended?","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 (E05030)\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: E05030, not open to 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":"#!ek9\ndefines 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":["E05030","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\n","incorrect":"    Rectangle\n","explanation":"Rectangle declares override area(), perimeter(), sides() and describe(), but without its 'extends Polygon' there is no parent version of any of them to override, so the compiler reports E05110 (nothing to override). Dropping the super (or extending an unrelated type) orphans every override. 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 (E08100, E08120)\nPure methods cannot mutate fields. Assigning a field with ':=' (or ':') inside a pure method triggers E08100; using a mutation operator such as '+=' on a collection 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":"#!ek9\ndefines 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":["E05190","E08130"],"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 E05110 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' explicitly; 'allow only' does not imply it. 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 (E05110)\nChild classes can override parent methods. Using 'override' on a method that does not exist in the parent triggers E05110.\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":"#!ek9\ndefines 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","E05110","E05270","allow","circular","class","closed","exhaustive","hierarchy","inherit","only","permitted","sealed","type"],"primaryTopics":["sealed classes","E05050"],"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 E06150 conflicting methods from multiple traits?","Why do I get E06140 ambiguous match when calling a method two traits both define?","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 (E06150)\nIf TraitA declares process() and TraitB also declares process(), a class implementing both has a conflict. The compiler reports E06150 (conflicting methods to be resolved) on the class unless the class provides an override.\n\nAMBIGUOUS CALLS (E06140)\nWhile the conflict is unresolved, every call to that method on the class is also reported, with E06140 (ambiguous match), naming both trait versions.\n\nA SHARED TRAIT IS NOT A CONFLICT\nIf Walker and Swimmer both have trait of Named, a class with trait of Walker, Swimmer reaches Named's methods along two routes - but each is ONE method with one body, so there is nothing to resolve and no override is needed. It is a conflict only when the routes bring DIFFERENT bodies: Walker overrides Named's describe() and Swimmer does not, or the super class overrides it and a listed trait brings Named's.\n\nONE IMPLEMENTATION PLUS ABSTRACT DECLARATIONS IS NOT A CONFLICT\nIf Named's describe() is abstract, Walker implements it and Swimmer leaves it abstract, a class with trait of Walker, Swimmer has Walker's describe(): the implementation IS the method, it meets the abstract requirement, and no override is needed. The same holds when the super class implements it (Base with trait of Named) and a listed trait leaves it abstract (Sub extends Base with trait of Swimmer), or the other way round. It is still E06150 when two DIFFERENT implementations reach the class, when a 'by' delegate's route meets another route with a body (a delegate alone does implement an abstract method), or when the abstract method belongs to an unrelated trait that merely has the same signature; and a concrete class whose abstract method nothing implements is E07130.\n\nRESOLUTION VIA OVERRIDE\nThe class must provide its own 'override' of the conflicting method. This removes ambiguity by giving the class full control over the method body, and both errors go away.\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":"#!ek9\ndefines module qa.typehierarchy.diamondtrait\n\n  defines trait\n\n    <?-\n      First trait: declares formatRecord() with a default body.\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","E06140","E06150","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 E06010 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'. 'Container of type T' declares one parameter and 'Mapping of type (K, V)' declares two. The parameter names become placeholders for concrete types.\n\nINSTANTIATION SYNTAX\nConcrete types replace parameters with 'of'. 'Container of String' supplies one and 'Mapping of (String, Integer)' supplies two. The number of concrete types must match the declared parameters.\n\nMISSING PARAMETERS (E06010)\nOmitting required type parameters triggers E06010:\n  defines class\n    Container of type T\n      item as T: T()\n      default operator ?\n  defines program\n    Demo()\n      container <- Container()  // ERROR E06010: needs a type unless inferred from args\nThe correct form gives the explicit type:\n  container <- Container() of String\n\nVALUES PLUS 'of' (E06220)\nSupplying constructor values AND an explicit 'of' clause together is E06220:\n  defines class\n    Container of type T\n      item as T: T()\n      Container()\n        -> value as T\n        this.item: value\n      default operator ?\n  defines program\n    Demo()\n      container <- Container(42) of Integer  // ERROR E06220: infer the type from the value, or use Container() of Integer\nThe correct form lets T be inferred as Integer:\n  container <- Container(42)\n\nINFERRENCE FROM ARGUMENTS\nWhen constructor arguments match type parameters, inference works:\n  container <- Container(\"hello\")   // T inferred as String\n\nPARENTHESES FIRST (E06210)\n'Container of String()' puts the '()' after the type arguments; write 'Container() of String' - the compiler names the fix and the editing tools rewrite it.\n\nTARGET TYPING\n'x <- Container() of String' is the form to write; where the target's type is already declared, a bare construction is completed from it - 'container as Container of String: Container()', and the same for ':=', ':=?', a returning variable, a property or a field reached through an object ('this.box := Container()') - but never with '<-', which declares no type, and not in a ternary arm or an argument.\n\nSee Q642 for constructor inference. See Q654 for parameter count validation. See Q656 for argument count validation.","ek9Example":"#!ek9\ndefines 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      //A declared type completes a bare construction\n      targetHolder as Holder of Integer: Holder()\n      stdout.println(`Target-typed holder set: ${targetHolder?}`)\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":"The parentheses belong to the generic's name, before the type arguments: 'Holder of String()' reads as a call of the type 'Holder of String'. Write 'Holder() of String'. See ek9 -h E06210 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  defines class\n\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      getContent() as pure\n        <- rtn as T: content\n\n      default operator ?\n\n  defines program\n\n    Demo()\n      stdout <- Stdout()\n      box <- Box(42)\n      stdout.println(`${box.content}`)       // ERROR E06180: private field\n      stdout.println(`${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 Q1286 for public constructor requirement. See Q642 for constructor inference. See Q651 for constraint constructor match.","ek9Example":"#!ek9\ndefines 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: 0\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    Container() as pure\n      -> item as T\n      this.item :=? item\n\nPARAMETER TYPE MATCHING (E06050)\nConstructor parameters must use the declared type parameters:\n  Box of type T\n    held as T?\n    Box() as pure\n      -> item as T        // Correct: parameter is T\n      held :=? item\n    Box() as pure\n      -> item as String   // ERROR E06050: must use T\n      held :=? 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() as pure              // inferred, public\n      -> item as T\n      this.item :=? item\n\nSee Q642 for constructor inference. See Q643 for two-constructor requirement. See Q1286 for the private no-arg escape hatch.","ek9Example":"#!ek9\ndefines 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":["E06040","E06050"],"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 happens if a service URI path is written without the leading colon?","What is E02070 duplicate service endpoint?","What is E07730 path variable and parameter count mismatch?","What types can a service path parameter have?"],"answer":"EK9 services map URI paths to methods and operators. Several rules govern valid service definitions, all checked at compile time to prevent runtime routing errors.\n\nURI PATH SYNTAX\nThe base path is declared on the service name ('ProductService for :/products'), and each method adds a relative path ('featured() as GET for :/featured'), both written with ':/'.\nA path written without the colon ('for /featured') is not a URI path at all: it is a syntax error, reported before any semantic check.\n\nNO DUPLICATE ENDPOINTS (E02070)\nTwo methods or operators cannot map to the same HTTP verb and path structure. Both clashing methods are reported with E02070 ('PATH is duplicated in terms of structure/naming').\n\nPATH PARAMETER MATCHING (E07730, E07700)\nEvery '{name}' in the path needs a method parameter, and every PATH parameter needs a '{name}' in the path: 'findById() as GET for :/{productId}' takes '-> productId as String'. Leaving the parameter out of the method gives E07730 (PATH variable count and PATH parameter count mismatch). A parameter whose '{name}' is not in the path is E07700.\n\nPATH PARAMETER TYPES (E07760)\nA path parameter does not have to be a String. Integer, String, Date, Time, DateTime, Milliseconds and Duration are converted from the URI for you; anything else (Float, for example) is E07760.\n\nSee Q657 for URI mapping basics. See Q658 for path parameter binding. See Q659 for service return types.","ek9Example":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`[\"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 Bytes: Bytes().withUtf8(`{\"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 Bytes: Bytes().withUtf8(`{\"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":["E02070","E07700","E07730","E07760","REST","URI","dict","duplicate","endpoint","function","http","path","service"],"primaryTopics":["service uri paths","E02070","E07730"],"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 E07740 service method with no body?","What is E07240 protected method in a service?","Can a service have private helper methods?","What restrictions apply to service method implementations?"],"answer":"EK9 service methods must have concrete implementations, and one access modifier is restricted.\n\nCONCRETE BODIES REQUIRED (E07740)\nEvery service method and operator must have a body. A method declared with no implementation is E07740 ('implementation not provided, but services cannot be abstract'): the runtime must be able to invoke every endpoint.\n\nHELPER METHODS\nA service may also have methods that are not web endpoints (no HTTP verb and path), plain or 'private', for logic shared by its endpoints. Marking such a helper 'protected' is E07240: nothing can extend a service, so 'protected' has no meaning there.\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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"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 Bytes: Bytes().withUtf8(`{\"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 (E07740) and return HTTPResponse; non-web helper methods may be plain or private, never protected (E07240).","keywords":["E07240","E07740","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":"Must an override keep the 'sanitized' modifier of its parent?","url":"https://ek9.io/qa/QA0686.html","alternatePhrasings":["Is E07940 raised when an override adds sanitized?","Why must a child keep the parent's sanitized modifier?","Must sanitized match exactly when overriding a method?","What happens if I remove sanitized from an override?"],"answer":"Yes. If a parent method marks a parameter 'sanitized', every override must keep it (E07940). An override may also ADD 'sanitized' to a parameter the parent leaves plain - that is allowed.\n\nDROPPING IS REJECTED (E07940)\nRemoving 'sanitized' in an override creates a vulnerability: callers holding the parent type expect their input to be cleaned, but through polymorphism the override would receive it raw.\n\nADDING IS ALLOWED\nThe receiving method cleans its own 'sanitized' parameters, so an override that adds 'sanitized' cleans its input on every route, including a call through the parent type. A subtype may weaken a precondition, never strengthen it (Liskov Substitution).\n\nWHY THIS MATTERS\n  handler <- getHandler()        // Could be Base or Derived\n  handler.process(userInput)     // Base promised to clean userInput\nBecause no override can drop 'sanitized', the promise holds whichever runtime type is active.\n\nSAFE COPY PATTERN\nInside methods with sanitized parameters, create a safe copy before processing (a direct 'local <- param' is E07930):\n  safeCopy <- String(inputText)\n  result: transform(safeCopy)\n\nSee Q663 for adding and dropping sanitized in detail. See Q661 for sanitized parameter basics. See Q662 for safe copy patterns.","ek9Example":"#!ek9\ndefines 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: the compiler rejects an override that drops sanitized, keeping the parent's security contract 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)\n        cleanResult: \"Handled: \" + safeCopy","incorrect":"safeCopy <- userInput\n        cleanResult: \"Handled: \" + safeCopy","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 guards?","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 declaration guards 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 declaration guards. See Q636 for chained guards. See Q306 for Result type basics.","ek9Example":"#!ek9\ndefines 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: guards 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":["E08030","E08040"],"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":"#!ek9\ndefines 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":["E08010","E08020"],"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 E07620 type mismatch in comparison?","What is E07390 boolean literal in condition?","What type does a ternary expression produce when the branches differ?","How should boolean conditions be written in EK9?"],"answer":"EK9 enforces clean boolean condition patterns. Conditions must use variables (not literals) and comparisons must have matching types. A ternary expression is typed to a type its branches share: the same type, one branch promoting to the other's type, or their nearest common super or trait - never an invented Any.\n\nNO BOOLEAN LITERALS IN CONDITIONS (E07390)\nUsing 'true' or 'false' directly in a condition is flagged as a pointless constant:\n  if true                 // E07390: always true, dead code\n    stdout.println(\"always\")\n\nThe correct form tests a boolean variable:\n  if isValid\n    stdout.println(\"valid\")\n\nTYPE MATCHING IN COMPARISONS (E07620)\nBoth sides of a comparison must be compatible types:\n  if count > 0\n    stdout.println(count)\n\nThis does not compile:\n  name <- \"Steve\"\n  if name == 42          // E07620: String has no == for Integer\n    stdout.println(name)\n\nTERNARY TYPES\nThe result is one of the branches, so it must have a type both branches have. The same type or a subtype gives that type; a branch that promotes to the other's type (Integer to Float) gives the other's type; otherwise the nearest common super class or trait. Branches that share nothing but Any are a compile error:\n  age <- 20\n  label <- age >= 18 <- \"adult\" else 0     // ERROR E50030: String and Integer share only Any\n\nGive both branches one type:\n  label <- isAdult <- \"adult\" else \"minor\"\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":"#!ek9\ndefines 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":["E07390","E07620","boolean","comparison","condition","literal","pattern","ternary","type"],"primaryTopics":["E07620","E07390","ternary type"],"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?","Why can't I call a non-pure function from a pure function?","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":"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 (E08100, E08120, E08130)\nPure functions cannot mutate any state:\n  - Cannot reassign fields with ':=' or ':' (E08100)\n  - Cannot use += on collections (E08120)\n  - Cannot call methods that mutate state (E08130)\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":"#!ek9\ndefines 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","impure","mutation","pure","purity","restriction","side effect","side-effect","transitive"],"primaryTopics":["purity chain","E08130","pure calling"],"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 (E08210)\nEvery abstract component that is injected must have a concrete implementation registered in the application. A missing registration triggers E08210 ('... which is never registered in this application').\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\nREGISTRATION ORDER (E08200)\nRegistrations are made in the order they are written, so each dependency must be registered BEFORE the component that injects it. Registering a dependent first triggers E08200 ('... which is not yet registered, reorder registrations'):\n  register OrderCoordinator() as Coordinator        // E08200: needs OrderService, not yet registered\n  register DefaultOrderService() as OrderService    // E08200: needs Repository, not yet registered\n  register InMemoryRepository() as Repository\nFix: register bottom-up - Repository, then OrderService, then Coordinator - as the example below does.\n\nABSTRACT INJECTION RULE\nDI only works with abstract components:\n  defines component\n    DataAccess as abstract\n      load() as abstract\n        <- rtn as String?\n      default operator ?\n\n    DatabaseAccess extends DataAccess\n      override load()\n        <- rtn as String: \"row\"\n      default operator ?\n\nDataAccess can be injected; DatabaseAccess is the concrete implementation registered for it.\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":"#!ek9\ndefines 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 (a missing one is E08210),\n      each dependency before the component that injects it (out of order is 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","E08210","application","compile-time","component","dependency","inject","injection","order","registration","sanitize","validate"],"primaryTopics":["E08200","E08210","component registration ordering"],"typicalErrors":[{"error":"E08210","correct":"OrderApp\n      register InMemoryRepository() as Repository\n      register DefaultOrderService() as OrderService\n      register OrderCoordinator() as Coordinator","incorrect":"OrderApp\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; a missing registration in the chain is E08210. See ek9 -h E08210 for details."},{"error":"E08200","correct":"      register InMemoryRepository() as Repository\n      register DefaultOrderService() as OrderService\n      register OrderCoordinator() as Coordinator","incorrect":"      register OrderCoordinator() as Coordinator\n      register DefaultOrderService() as OrderService\n      register InMemoryRepository() as Repository","explanation":"Each dependency must be registered before the component that injects it. Registering OrderCoordinator before OrderService (and DefaultOrderService before Repository) is E08200: reorder the registrations bottom-up. See ek9 -h E08200 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. A field declared with a type but neither an initialiser nor '?' (name as String) triggers E08180.\n\nINLINE INITIALIZATION\nThe simplest approach initializes fields at declaration:\n  name <- String()             // present but unset (use \"\" for an empty string)\n  count as Integer: Integer()  // explicit type, present but unset\n  host <- \"localhost\"         // present and set\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; it relies on the fields' own initialisers.\n\nUNINITIALISED '?' FIELDS\nA field declared with '?' has no initial value at all:\n  name as String?\nEvery construction route must then set it (SearchResult's constructor does). If none does, the compiler reports E08070 (never initialised) and E07170 (a developer-coded constructor is required). To make a field present but unset from the start, give it an initialiser instead: name as String: String().\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":"#!ek9\ndefines 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":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 a call passes 2 or more Boolean LITERALS positionally, and when a call passes 4 or more arguments.\n\nBOOLEAN POSITIONAL LITERALS (E11061)\nTwo or more Boolean literals in one call are ambiguous when positional:\n  setMode()\n    ->\n      verbose as Boolean\n      debug as Boolean\n    <- rtn as String: `verbose=${verbose} debug=${debug}`\n\n  stdout <- Stdout()\n  stdout.println(setMode(true, false))                  // ERROR E11061: which is which?\n  stdout.println(setMode(verbose: true, debug: false))  // Correct: named\nA single positional Boolean literal (formatOutput(\"hello\", true)) is accepted, and so are Boolean variables passed positionally (setMode(verboseOn, debugOff)) - the variable names already say what they mean. Naming a single Boolean literal is still clearer.\n\n4+ POSITIONAL ARGS (E11062)\nA call that passes 4 or more arguments positionally is rejected - name them:\n  configure()\n    ->\n      host as String\n      port as Integer\n      secure as Boolean\n      user as String\n    <- rtn as String: `${user}@${host}:${port} secure=${secure}`\n\n  stdout <- Stdout()\n  stdout.println(configure(\"db\", 8080, true, \"admin\"))                         // ERROR E11062\n  stdout.println(configure(host: \"db\", 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\nA call with 1-3 arguments can be positional, as long as it does not pass 2 or more Boolean literals:\n  greet(\"Alice\", \"morning\")  // OK: only 2 args\n\nSee Q314 for naming conventions. See Q322 for code quality metrics. See Q689 for boolean condition patterns.","ek9Example":"#!ek9\ndefines 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 a Boolean parameter: a single Boolean literal may be positional,\n      but naming it is clearer.\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 is clearer (required only for 2+ Boolean literals)\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 calls with 4+ arguments (E11062) and for 2+ positional Boolean literals (E11061).","keywords":["Boolean","E11061","E11062","arguments","clarity","clean-code","function","metric","named","parameter","positional","quality"],"primaryTopics":["E11061","E11062"],"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 literals (other than 0, 1, -1, 0.0 and 1.0) in comparisons are flagged. String literals are not flagged by this rule:\n  isBoiling() as pure\n    -> temperature as Float\n    <- rtn as Boolean: temperature > 100.0   // E11064: what does 100.0 mean?\nReplace the literal with a named constant:\n  isBoiling() as pure\n    -> temperature as Float\n    <- rtn as Boolean: 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\nInteger and Float literals in comparison expressions (==, <>, <, >, <=, >=, <=>, contains, matches and ranges) are checked. The values 0, 1, -1, 0.0 and 1.0 are exempt. 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":"#!ek9\ndefines 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 numeric literals in comparisons.","keywords":["E11064","clean-code","comparison","constant","literal","magic","metric","migrate","named","quality","self-documenting"],"primaryTopics":["magic literals","E11064"],"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 too many statements?","How do I keep functions within complexity limits?","What are the complexity thresholds in EK9?"],"answer":"EK9 measures cyclomatic complexity per function, method and operator, and also budgets the number of statements. Exceeding either limit triggers a compile error, forcing decomposition into smaller units.\n\nCYCLOMATIC COMPLEXITY (E11010)\nEach branch point (if, switch case, for, while, guard) adds to complexity. The compiler sets a threshold and rejects any function, method or operator that exceeds it. Methods in classes are checked individually and also raise E11010.\n\nSTATEMENT COUNT (E11012)\nSeparately, E11012 is a statement-count budget (for example 50 statements in an operator, 100 in a method, 150 in a function). A body with too many statements triggers E11012 even when its branching is simple.\n\nSTAYING WITHIN LIMITS\n1. Decompose into smaller pure functions\n2. Use guards 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":"#!ek9\ndefines 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":["cyclomatic complexity","E11010","E11012"],"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":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 call sites that pass Boolean literals in a way that creates unclear code. Having a Boolean parameter is not an error by itself.\n\nPOSITIONAL BOOLEAN LITERALS (E11061)\nA call that passes two or more Boolean literals positionally is rejected. Name the arguments to avoid ambiguity:\n  process()\n    ->\n      verbose as Boolean\n      debug as Boolean\n    <- rtn as Boolean: verbose and debug\n  r1 <- process(true, false)           // E11061: ambiguous\n  r2 <- process(verbose: true, debug: false)  // Clear\n  stdout.println(`${r1} ${r2}`)\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":"#!ek9\ndefines module qa.codequality.booleanparams\n\n  defines function\n\n    <?-\n      Function with Boolean parameter.\n      Callers should name the Boolean arguments (two positional Boolean literals are E11061).\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: a call passing two or more positional Boolean literals is a compile error (E11061); name them.","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 E05110 parameter type mismatch in override?","What is E07440 incompatible return type in override?","Must override methods have identical signatures?"],"answer":"An override keeps the parent's PARAMETER types exactly, keeps its purity, and may NARROW its return type to a subtype (a covariant return).\n\nRETURN TYPE: SAME OR NARROWER (E07440)\nThe return type of an override must be the parent's type or a subtype of it:\n  Parent: duplicate() <- rtn as Formatter\n  Child:  override duplicate() <- rtn as Formatter          // Correct\n  Child:  override duplicate() <- rtn as DetailFormatter    // Correct: covariant\n  Child:  override duplicate() <- rtn as Integer            // E07440\nAn unrelated return type is E07440 ('return types are incompatible (covariance required)'). Code holding a Formatter reference still gets a Formatter back, so the narrowed override is safe - and it is the override that runs.\n\nPARAMETER TYPES: EXACTLY THE SAME (E05110)\nParameter types must match exactly:\n  Parent: handle(input as String)\n  Child:  override handle(input as String)    // Correct\n  Child:  override handle(input as Integer)   // E05110\nDifferent parameter types make a new overload, not an override, so 'override' on it is E05110.\n\nPURITY IS KEPT (E05150, E05160)\nA pure parent method requires a pure override (E05150). If 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\nAny code using the parent type works correctly with any descendant: it passes the same argument types, and it gets back something that IS the type it expects.\n\nSee Q570 for override basics. See Q573 for access modifier rules. See Q567 for purity chain inheritance.","ek9Example":"#!ek9\ndefines module qa.override.returntypecovariance\n\n  defines class\n\n    <?-\n      Base class defining the method contract.\n      Parameter types and purity set here; return types may be narrowed.\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      //Method returning the base type\n      duplicate()\n        <- rtn as Formatter: Formatter()\n\n      default operator ?\n\n    <?-\n      Correct overrides: parameter types and purity preserved exactly.\n      A return type may be narrowed to a subtype (covariant return).\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      //Correct: covariant return - DetailFormatter is a Formatter\n      override duplicate()\n        <- rtn as DetailFormatter: DetailFormatter()\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\"))\n\n      //Through a Formatter reference, the covariant override still runs\n      asBase as Formatter: DetailFormatter()\n      stdout.println(`Duplicate: ${asBase.duplicate().summarize()}`)","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: like Java - exact parameter types (E05110), covariant (narrowed) return types allowed, an unrelated return type is E07440.","keywords":["E05110","E05160","E07440","abstract","covariance","function","inherit","open","override","parameter","return","signature","type","virtual"],"primaryTopics":["E05160","E05110"],"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 triggers E05150. (The reverse - adding purity to an override of a non-pure method - is E05160.) 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 be the parent's type or a subtype of it (covariant). Integer is unrelated to String, so the override 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":["Is there an error for too many parameters in EK9?","Does EK9 reject lines that are too long?","How many parameters can a function have?","What is the maximum line length in EK9?"],"answer":"EK9 has no direct parameter-count limit and no line-length check. What it does check is how calls pass their arguments, and parameter lists that repeat across functions.\n\nWHAT IS CHECKED\n- E11062: a call that passes 4 or more arguments positionally - name them (Q694).\n- E11061: a call that passes 2 or more Boolean literals positionally - name them (Q694).\n- E11010: more than 20 arguments in a single call is a hard stop ('arguments is too many').\n- E11053: a data clump - the same group of parameters repeated across functions - extract a record to group them.\nA function declared with 7 parameters compiles, as long as its callers name the arguments.\n\nLINE LENGTH\nThe compiler never rejects a long line. EK9's formatter wraps lines at 120 characters, but that is formatting, not a compile error.\n\nNOT THESE CODES\nE11020 is combined complexity and size (complexity ratio x statement ratio above 0.50), and E11040 is too many injected fields. Neither is about parameters or line length.\n\nCORRECT PATTERNS\n1. Group related parameters into a record or class (as ShippingAddress does below)\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 a createOrder with seven separate parameters (name, street, city, postal code, item, quantity, price), group them into meaningful types - a Customer record and an OrderItem record - so createOrder takes two parameters.\n\nSee Q316 for complexity metrics. See Q696 for complexity limits. See Q694 for named arguments.","ek9Example":"#!ek9\ndefines 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: no parameter-count or line-length error; calls with 4+ positional arguments (E11062) or 20+ arguments (E11010) and repeated parameter groups (E11053) are compile errors; the formatter wraps at 120 characters.","keywords":["E11010","E11053","E11062","clean-code","count","length","limit","line","metric","parameter","quality","readable"],"primaryTopics":["parameter count","line length"],"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":"#!ek9\ndefines 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":"E11080","correct":"env <- EnvVars()\n\n      //sensitiveGet() is the ONLY way to create a set Sensitive\n      apiKey <- env.sensitiveGet(\"API_KEY\")","incorrect":"apiKey <- \"AKIAIOSFODNN7EXAMPLE1\"","explanation":"A hardcoded cloud provider credential such as an AWS access key starting with 'AKIA' is detected at compile time (E11080). Use EnvVars.sensitiveGet() to load secrets from environment variables instead. See ek9 -h E11080 for details."},{"error":"E11086","correct":"env <- EnvVars()\n\n      //sensitiveGet() is the ONLY way to create a set Sensitive\n      apiKey <- env.sensitiveGet(\"API_KEY\")","incorrect":"apiKey <- \"sk_test_abcdefghijklmnopqrstuvwxyz\"","explanation":"A hardcoded API key such as a Stripe key with the 'sk_test_' prefix is detected at compile time (E11086). Load API keys from environment variables using EnvVars.sensitiveGet(). See ek9 -h E11086 for details."},{"error":"E11083","correct":"env <- EnvVars()\n\n      dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")","incorrect":"dbPassword <- \"postgres://admin:s3cret@db.example.com/prod\"","explanation":"A database URL with an embedded password is detected at compile time (E11083). Store the connection string in an environment variable. See ek9 -h E11083 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 declaration guards 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":"#!ek9\ndefines 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.\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":["sensitiveGet","load credentials"],"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":"#!ek9\ndefines 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      default operator ?\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    //The literal names the environment variable - it is never the secret itself\n    testLoadByName()\n      stdout <- Stdout()\n      env <- EnvVars()\n      keyName <- \"SERVICE_TOKEN\"\n      token <- env.sensitiveGet(keyName)\n      if token?\n        stdout.println(\"Loaded by name: \" + 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      testLoadByName()\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":["privileged reveal","sensitive reveal","unwrap sensitive"],"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":"E11086","correct":"keyName <- \"SERVICE_TOKEN\"","incorrect":"keyName <- \"sk_test_abcdefghijklmnopqrstuvwxyz\"","explanation":"The literal passed to sensitiveGet must be the NAME of the environment variable. Putting an API key such as a Stripe key with the 'sk_test_' prefix in source is detected at compile time (E11086). See ek9 -h E11086 for details."},{"error":"E11082","correct":"keyName <- \"SERVICE_TOKEN\"","incorrect":"keyName <- \"-----BEGIN RSA PRIVATE KEY-----\"","explanation":"Private key material must not be hardcoded: a PEM private key header in a String literal is detected at compile time (E11082). Load private keys from environment variables or files at runtime. See ek9 -h E11082 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_abcdefghijklmnopqrstuvwxyz\"}`   //ERROR E11086\n  stdout.println(msg)\nA literal too short to match the provider's pattern is not reported, so the scan reads the key's real shape, not its prefix alone.\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":"#!ek9\ndefines 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":["hardcoded secrets","secret detection","credential sast"],"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, such as AWS_ACCESS_KEY_ID, STRIPE_SECRET_KEY or 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\nSet the variable outside the source, with an export of AWS_ACCESS_KEY_ID in the shell that starts the program, 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":"#!ek9\ndefines 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":"E11080","correct":"env <- EnvVars()\n\n      //AFTER: Load from environment variable\n      if awsKey <- env.sensitiveGet(\"AWS_ACCESS_KEY_ID\")","incorrect":"if awsKey <- \"AKIAIOSFODNN7EXAMPLE1\"","explanation":"An AWS access key starting with 'AKIA' is detected at compile time (E11080). Extract the key to an environment variable and load it with sensitiveGet(). See ek9 -h E11080 for details."},{"error":"E11086","correct":"env <- EnvVars()\n\n      //AFTER: Load from environment variable\n      if stripeKey <- env.sensitiveGet(\"STRIPE_SECRET_KEY\")","incorrect":"if stripeKey <- \"sk_test_abcdefghijklmnopqrstuvwxyz\"","explanation":"A Stripe API key with the 'sk_test_' prefix is detected at compile time (E11086). Load API keys from environment variables using sensitiveGet(). See ek9 -h E11086 for details."},{"error":"E11081","correct":"env <- EnvVars()\n\n      //For test tokens: load from environment\n      if testToken <- env.sensitiveGet(\"TEST_JWT_TOKEN\")","incorrect":"if testToken <- \"ghp_ABCDEFabcdef1234567890abcdef12345678\"","explanation":"A GitHub personal access token with the 'ghp_' prefix is detected at compile time (E11081). Store platform tokens in environment variables. See ek9 -h E11081 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":"E11082","correct":"env <- EnvVars()\n\n      //AFTER: Load key path or content from environment\n      if keyPath <- env.get(\"TLS_KEY_PATH\")","incorrect":"if keyPath <- \"-----BEGIN EC PRIVATE KEY-----\"","explanation":"EC private key material is detected at compile time (E11082). Load private keys from files or environment variables at runtime. See ek9 -h E11082 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":"#!ek9\ndefines 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":["database credentials","connection url credentials","service connection secrets"],"typicalErrors":[{"error":"E11083","correct":"secret <- env.sensitiveGet(dbPasswordKey)","incorrect":"secret <- \"postgres://admin:s3cret@db.example.com/prod\"","explanation":"A database URL with an embedded password is detected at compile time (E11083). Separate host/port/name configuration from the password and load the password with sensitiveGet(). See ek9 -h E11083 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":"E11086","correct":"secret <- env.sensitiveGet(dbPasswordKey)","incorrect":"secret <- \"sk_live_abcdefghijklmnopqrstuvwxyz\"","explanation":"An API key such as a Stripe live key is detected at compile time (E11086). Load API keys for external services from environment variables using sensitiveGet(). See ek9 -h E11086 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    ->\n      item as String\n      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":"#!ek9\ndefines 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      //An Acceptor (or a Consumer) can be handed to Optional via whenPresent\n      opt <- Optional(\"Steve\")\n      opt.whenPresent(printer)","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 is free of side effects (it cannot mutate or reassign anything it does not own):\n  factory <- () is Supplier of String as pure function\n    r: \"Default\"\nA pure Supplier is a side-effect-free factory. Pure does not mean deterministic: pure-marked built-ins such as GUID() or SystemClock() can be called from a pure Supplier, which then returns a different value each time.\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 side-effect-free 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":"#!ek9\ndefines 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, side-effect free) 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 side-effect-free test:\n  check <- () is Predicate of Integer as pure function\n    r: t > 0\nIt cannot change anything as it runs. (Pure means side-effect free, not deterministic: a pure Predicate may still call pure-marked built-ins whose results vary.)\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    ->\n      item as Integer\n      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":"#!ek9\ndefines 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, side-effect-free 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 free of side effects:\n  mapper <- () is Function of (String, Integer) as pure function\n    r: length t\nIt cannot change anything as it runs. (Pure means side-effect free, not deterministic: a pure Function that calls a pure-marked built-in such as GUID() can return different outputs 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    ->\n      item as String\n      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":"#!ek9\ndefines 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, side-effect-free 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":"#!ek9\ndefines 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":["biconsumer","biacceptor","two-parameter consumer"],"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":"#!ek9\ndefines 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":["bipredicate","biassessor"],"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    ->\n      a as String\n      b as Integer\n      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":"#!ek9\ndefines 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":["bifunction","biroutine","two-input transformation"],"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":"#!ek9\ndefines 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":"natural <- () is Comparator of String as pure function","incorrect":"natural <- () 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    ->\n      item as String\n      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":"#!ek9\ndefines 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. Which type the result has depends on the operands.\n\nCT op CT RETURNS THE CONSTRAINED TYPE\nWhen you add two DrivingAge values, the result is DrivingAge, and the constraint is checked on it:\n  sum <- age1 + age2      //DrivingAge + DrivingAge -> DrivingAge\nIf that result breaks the constraint (age1 + DrivingAge(90) is 115), the program stops with an uncatchable Panic.\n\nCT op BaseType RETURNS THE BASE TYPE\nMixing in a plain Integer gives a plain Integer, and the constraint is NOT checked on it:\n  result2 <- age1 + 5      //DrivingAge + Integer -> Integer\n  product <- age1 * 10     //Integer 250, no Panic\nSo 'check as DrivingAge: age1 + 5' is E50030 (types are not compatible). Wrap the value (DrivingAge(age1 + 5)) when you need it back as the constrained type, and the constraint is checked then.\n\nMUTATING OPERATORS\nCompound assignment and increment keep the variable a DrivingAge, and the constraint is checked after each one:\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":"#!ek9\ndefines 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      // CAUTION: the RESULT must satisfy the constraint too. There is deliberately no\n      // 'age2 - age1' here - that is 5, below the minimum of 16, and a breach is an\n      // uncatchable Panic rather than an unset value. A gap between two ages is not an age.\n\n      // === MIXED: CT + BaseType -> BaseType (Integer, constraint NOT checked) ===\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      addValue <- 20\n      mutable += DrivingAge(addValue)\n      stdout.println(`22 += 20 = ${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 (CT op CT -> CT, constraint checked; CT op BaseType -> BaseType, unchecked), mutating operators keep the constrained type and check it, mod/rem return base type.","keywords":["add","arithmetic","compound","constrained","increment","integer","mutating","operator","range","subtract"],"primaryTopics":["constrained arithmetic","constrained math"],"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 == twentyFive   //DrivingAge == Integer -> Boolean\nThis means you can compare constrained values directly with values of the base type. A bare numeric literal such as 25 in a comparison is rejected by E11064 (give the literal a meaningful name first, as twentyFive <- 25 does); that rule is about numeric literals, so a String-constrained type can still be compared with a literal such as \"Alice\".\n\nWHY DUAL SIGNATURES MATTER\nWithout dual signatures, you would need to wrap every value, as in 'age1 == DrivingAge(25)' (works but verbose), whereas 'age1 == twentyFive' with a named Integer is 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.*/       //String has matches\nNote that matches tests the WHOLE string against the pattern, so /^A/ alone would be false for Alice while /^A.*/ is true.\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":"#!ek9\ndefines 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 base-type values (numeric literals still need a name, E11064).","keywords":["comparison","constrained","contains","dual","equals","inherit","matches","operator","signature"],"primaryTopics":["constrained comparison","dual signature","constrained base comparison"],"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":"#!ek9\ndefines 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":["constrained promote","#^"],"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 (:~:) FILLS, NEVER OVERWRITES\nMerge follows the one rule every type shares: ':~:' never overwrites a set value; it fills whatever is unset or missing. A constrained value is a single value, so it takes the source only when it is unset:\n  target :~: DrivingAge(80)\n  //target is set (70), so it stays 70\n  empty <- DrivingAge()\n  empty :~: DrivingAge(80)\n  //empty was unset, so it is now 80\nTo overwrite a set value use :=: or :^:.\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":"#!ek9\ndefines 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      //Merge never overwrites a set value - mergeTarget keeps 20\n      mergeTarget <- DrivingAge(20)\n      mergeSource <- DrivingAge(30)\n      mergeTarget :~: mergeSource\n      stdout.println(`After merge of 30 into 20: mergeTarget=${mergeTarget}`)\n\n      //An unset target is filled by a set source\n      emptyTarget <- DrivingAge()\n      emptyTarget :~: mergeSource\n      stdout.println(`After merge into unset: emptyTarget=${emptyTarget}`)\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":["constrained copy","constrained merge","constrained replace"],"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  Character — constrain as >= 'A' and <= 'Z'\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  Resolution — constrain as >= 300dpi\n  Money — constrain as >= 10000#GBP and <= 120000#GBP\n  Colour — constrain as #0099CC or #9900CC\n  Version — constrain as >= 1.0.0-0\n  Path — constrain as equality\n  RegEx — constrain as equality\n  Enumerations — constrain as \"Value1\" or \"Value2\"\n\nCONSTRAINABLE USER TYPES\nRecords and classes can be constrained, subject to TWO requirements.\n\n1. The base must have the operators used in the constraint, taking the literal's type:\n  ValidPerson as Person constrain as\n    matches /^[a-zA-Z]+ [a-zA-Z]+$/\nFields are NOT addressable inside a constrain block, so a multi-field rule must be folded into ONE operator on the base's public surface.\n\n2. A DEFAULT-CONSTRUCTED base must be UNSET. A constrained type has no unset state of its own - it borrows one from its base - so allocate the fields unset:\n  Point\n    x as Integer: Integer()   //allocated but UNSET - correct\n    y as Integer: 0           //always SET - leaves the constrained type unable to be unset\nThe synthesised '?' is AND-over-all-fields, so a single set-initialised field makes the whole record set, and 'of(...)' can then Panic or return the base's default in place of a rejected value.\n\nNON-CONSTRAINABLE TYPES\nThese CANNOT be constrained (E04010 unless stated):\n  Boolean — already has only two values\n  Bits — has comparison operators but no literal form usable in a constraint\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  An already-constrained type — constraining is ONE-LEVEL (see Q722)\n  Parameterised generics such as 'List of String' (E06132) — a collection is SET when created, so there is no unset state for the constrained type to borrow; and a whole-container predicate like 'contains' is a require condition, not a type invariant (a mutating '+=' would re-validate, so a mutation could break it). Constrain the ELEMENT type instead:\n    Tag as String constrain as\n      matches /^[a-z]+$/\n  then hold 'List of Tag'. That guarantee is stronger - it holds for every element however the list is assembled.\n\nWHY THESE RESTRICTIONS\nA constraint compares against a LITERAL, so the base needs both a literal form to compare with and the matching operator. A constrained type also needs an UNSET representation, which it takes from a default-constructed base - a base that is always set cannot supply one.\n\nSIMPLE ALIASING IS ALWAYS ALLOWED\nType aliasing without constraints works for any type:\n  defines 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":"#!ek9\ndefines 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    // === ELEMENT CONSTRAINT: the right tool for \"a list of only these values\" ===\n    // A parameterised generic cannot be a constrained base (E06132). Constrain the ELEMENT\n    // and hold a collection of it - the guarantee then holds for every element.\n    Tag as String constrain as\n      matches /^[a-z]+$/\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 the compile error E08260 whenever the value is statically KNOWN — written inline,\n      // held in a constant, or carried through a variable).\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      // === ELEMENT CONSTRAINT RATHER THAN A CONSTRAINED COLLECTION ===\n      tags <- List() of Tag\n      tags += Tag(\"alpha\")\n      stdout.println(`Tags: ${tags}, length ${length tags}`)\n\n      rejectedTag <- Tag().of(\"NOT-LOWER\")\n      stdout.println(`Rejected tag set: ${rejectedTag?}`)\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","E06132","bits","boolean","built-in","constrain","constrainable","date","element","generics","integer","list","money","string","types","unset"],"primaryTopics":["constrainable types","which types constrained","built-in constrained"],"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":"#!ek9\ndefines 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\n    > 0\n  //DBIndex as Index constrain as\n  //  < 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\n    > 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 (promote exists only when the base type defines #^, as Integer does; a String- or Money-based constrained type has no #^):\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":"#!ek9\ndefines 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 EXISTS WHEN THE BASE TYPE DEFINES #^ (Integer here), AND RETURNS THE BASE TYPE ===\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":["constrained hierarchy","constrained subtype","like-a constrained"],"typicalErrors":[{"error":"E04010","correct":"BoundedIndex as Integer constrain as\n      > 0 and < 1000000","incorrect":"DBIndex as Index constrain as\n      < 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- #?  (hashcode) - gets the hash code: #? name (the length is not #?, it is 'length name')\n- ~   (boolean negate) - the same as not: ~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- length (length) - gets length: length collection\n\nThe empty check is NOT a prefix operator: write it as a test, 'collection is empty'.\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":"#!ek9\ndefines 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":["dollar prefix","name$ wrong"],"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\nDECLARATION GUARDS\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":"#!ek9\ndefines 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 (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":"#!ek9\ndefines 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 the compiler (E01086): 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())\n\n      item.applyDiscount(10.0)","incorrect":"stdout.println(item.describe().toUpperCase())\n\n      item.applyDiscount(10.0)","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 ('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":"#!ek9\ndefines 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":["E01010","reference qualifier"],"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":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\nEach level of nesting is one more condition a reader must hold in mind. At 6 levels, a reader must track 6 simultaneous conditions to understand which branch executes, and every further level adds to the maintenance and review burden.\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 guards 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":"#!ek9\ndefines 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":["nesting depth","E11011","control flow nesting"],"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":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":"#!ek9\ndefines 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":["E11054","traversal limit"],"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?","Why must operator and pure function return values be captured?","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, 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 Q310 for code quality overview.","ek9Example":"#!ek9\ndefines 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","Result","boundary","capture","clean-code","code","constructor","dead","discard","function","immutable","metric","operator","pure","quality","return","side-effect"],"primaryTopics":["discarded return","E11050","E11051"],"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":"#!ek9\ndefines 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":["E11020","combined complexity","complexity size"],"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":"#!ek9\ndefines 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":["E08084","E08085"],"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":"#!ek9\ndefines 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":["E08086","E08087"],"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 the compiler has already proved a variable is unset, checking it with ? again is always false. For example, in the else branch of 'if name?' a second 'else if name?' can never succeed, so the code inside it is dead code. A variable declared with '?' and never assigned is a different error: using it is E08020 (might be used before being initialised).\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":"#!ek9\ndefines 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":["E08089","E08092"],"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)\nIn a pure function, ':=' on a local (non-returning) variable is rejected. Pure functions should compute results, not accumulate state. Two forms stay allowed: ':=?' (assign-if-unset, only sets if unset) and assignment to the return variable (rtn := start, or label: \"excellent\" as classifyScore does below). Mutating operators such as += are a separate ban in a pure scope (E08120), so they are not a way round E08100.\n\nPARAMETER REASSIGNMENT (E08110)\nYou cannot REASSIGN a parameter (rawName := ...). The parameter names the caller's input, and pointing it at something else obscures the data flow. Instead, copy the parameter to a local variable and change the copy, as formatName does below.\nReassignment is not the same as mutation: in a non-pure function a parameter CAN be changed in place with a mutating operator (counterValue += 1, items += \"x\"), and the caller SEES that change, because the caller and the function share the same object.\n\nWHY ENFORCED\nReassignment creates hidden state changes that are hard to reason about. Pure functions should be deterministic and side-effect free. Banning parameter reassignment keeps a parameter name meaning the caller's object for the whole body.\n\nSee Q678 for purity contracts. See Q635 for pure mutation restrictions.","ek9Example":"#!ek9\ndefines 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: a parameter cannot be reassigned (E08110) but can be mutated in place outside pure scopes, visibly to the caller; a pure scope rejects ':=' on locals (E08100) but allows ':=?' and assigning the return variable, and rejects mutating operators (E08120).","keywords":["E08100","E08110","context","parameter","pure","quality","reassignment","restriction"],"primaryTopics":["E08100","E08110"],"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 cannot be reassigned with :=. Copy the parameter to a local variable first, then change 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":"#!ek9\ndefines 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 property in EK9?","Does default operator $$ change whether a child may reuse a parent property name?","Why does EK9 reject properties with the same name in parent and child?"],"answer":"EK9 rejects a property name that appears twice in a type hierarchy, at compile time.\n\nDUPLICATE PROPERTY FIELD (E02010)\nA child record, class, component or dynamic class cannot redeclare a property with the same name as a property anywhere up its super chain, whatever its type. Class and component properties are private, but a redeclared one would still be a second field: the parent's methods would read one copy and the child's methods the other.\n\nJSON ($$) IS COVERED BY THE SAME RULE\nBecause no name can appear twice in a hierarchy, a generated $$ (to JSON) operator can never meet a duplicate key. There is no separate JSON error: redeclaring the property is E02010 whichever operators are defaulted.\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":"#!ek9\ndefines 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","collision","duplicate","hierarchy","json","name","property","quality","shadow"],"primaryTopics":["E02010","duplicate property"],"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\nREDUNDANT TRAIT REFERENCE (E02090)\nListing a trait that another listed trait already has - 'with trait of LoudSpeaker, Speaker' where LoudSpeaker is a Speaker - adds nothing: list only the trait that has the other. A trait the super class already has is the same error: 'Announcer extends Base with trait of Speaker', where Base is a Speaker, is written 'Announcer extends Base'. Two different listed traits that share an unlisted trait (a diamond) are fine.\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":"#!ek9\ndefines 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":["E02050","E02070"],"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":"#!ek9\ndefines 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":["E03020","E03030","unresolved reference"],"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":"#!ek9\ndefines 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":["E04040","E04070","not a template"],"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":"#!ek9\ndefines 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":["this super","super restrictions","this restrictions"],"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 reject constructing a program or application, and abstract types in sealed 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)\nA program or an application cannot be created with a constructor call ('app <- OrderApp()'). A program is the entry point the runtime starts, and an application is the dependency-injection blueprint that a program binds with 'with application of'; neither is an ordinary object.\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\nBind a program to its application with 'with application of' instead of constructing either. For sealed traits, list only concrete classes in the allow only clause.\n\nSee Q679 for sealed allow only. See Q1312 for programs and applications.","ek9Example":"#!ek9\ndefines 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 the program/application genus rule and sealed trait allow only lists.","keywords":["E05200","E05250","abstract","allow","constraint","constructor","generic","only","quality","sealed"],"primaryTopics":["E05200","E05250"],"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":"#!ek9\ndefines 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":["parameter validation","named arguments","argument mismatch"],"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    Hue: Red, Green, Blue\n  labels <- {Hue.Red: \"#FF0000\", Hue.Green: \"#00FF00\", Hue.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":"#!ek9\ndefines module qa.enums.swiftmigration\n\n  defines type\n\n    Hue\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 <- {Hue.Red: \"#FF0000\", Hue.Green: \"#00FF00\", Hue.Blue: \"#0000FF\"}\n\n      for colour in Hue\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 declaration guards 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 declaration guard 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 <- lookup.getOrDefault(\"default\")   // lookup is an Optional of String\nFor an Optional, use getOrDefault. EK9's ?: is for a plain value that may be unset (it checks isSet) - applied to an Optional of String with a String on the right it yields Any, not String. EK9 also has ?? for absent-only checks, plus <? and >? for comparison coalescing operators.\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 assign-if-unset. See Q168 for fallback values. See Q243 for coalescing operators.","ek9Example":"#!ek9\ndefines 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: declaration guards (if x <- expr()) for binding, ?: for coalesce (Elvis), ?? 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?","What is the EK9 equivalent of Swift repeat-while?"],"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, Level.Critical\n              if temp > threshold\n                alert()\n            case Level.Low, Level.Medium\n              monitor()\n            default\n              stdout.println(\"Unknown level\")\nEK9 switch cases use nested if for additional conditions. A switch on an enumeration must cover every value (E07310) and still needs a default (E07320), which handles an unset value.\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 { body } while condition\n  EK9:    do\n            body\n          while condition\nEK9's do-while runs the body first and checks the condition after, so the body always runs at least once. It can start with a guard (do x <- init()) and can return a value as an expression (see Q67 and Q81).\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":"#!ek9\ndefines 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      // Swift: case .high where temp > 100: alert()\n\n      temperature <- 120\n      alertTemperature <- 100\n      for lvl in Level\n        currentAction <- responseAction(lvl)\n        switch lvl\n          case Level.High, Level.Critical\n            if temperature > alertTemperature\n              stdout.println(`ALERT: ${lvl} requires ${currentAction}`)\n            else\n              stdout.println(`WATCH: ${lvl} at ${temperature}`)\n          case Level.Low, Level.Medium\n            stdout.println(`OK: ${lvl} requires ${currentAction}`)\n          default\n            stdout.println(`Unknown level: ${lvl}`)\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: declaration guards (if x <- expr()), nested if for switch conditions, do/while for repeat-while, 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 guards 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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      //The sameness: < (and == <> <= > >=) are generated from it\n      operator <=> as pure\n        -> other as Temperature\n        <- rtn as Integer: this.reading <=> other.reading\n\n      operator #? as pure\n        <- rtn as Integer: #? this.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":["run fuzzer","-fuzz","fuzz crashes"],"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, 15 if one or more compiler crashes were found (every format exits the same way).\n\nJSON (-fuzz2)\nStructured output for programmatic analysis:\n- fuzz-report.json holds the final summary with crash count, iterations and timing.\n- fuzz-snapshots.jsonl holds periodic snapshots, suitable 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 coverage grid, phase funnel and distribution charts, strand comparison charts, crash and limitation counts.\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\nExit code 15 means the fuzzer found a compiler crash: fail the build on it. It is deliberately not 11, which means a unit test failed.\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":"#!ek9\ndefines 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":["-fuzz0","fuzz formats"],"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 . Summary cards (including the programs-per-second rate) sit at the top, followed by sections on the pipeline, error coverage, the generation strands and crashes.\n\nPHASE PENETRATION FUNNEL AND PHASE DISTRIBUTION\nThe funnel shows what fraction of the generated programs survive to each compilation phase (PARSING, SYMBOL_DEFINITION, TYPE_HIERARCHY_CHECKS, PRE_IR_CHECKS, etc.); the distribution shows where programs stop. A healthy fuzzer produces files that exercise all phases, not just the parser.\n\nERROR CODE COVERAGE\nA grid with one cell per compiler error code. Green cells were triggered (darker means more frequent), grey cells never were. You can search the grid and filter it to triggered or untriggered codes. An Untriggered Error Code Analysis section groups the missing codes by phase, which reveals gaps in the fuzzer's grammar coverage.\n\nCONVERGENCE AND STRANDS\nError Coverage Convergence shows how error coverage grows over the run. The Error-Strand Discovery Heatmap, the strand comparison charts and the Adaptive Weight Timeline show which generation strands found which errors and how their weights moved.\n\nCONSTRUCT COVERAGE\nWhich EK9 constructs (classes, records, traits, functions, services...) the generated programs exercised.\n\nCRASH AND LIMITATION BREAKDOWN\nSummary cards for compiler crashes and for known limitations (expected failures from unimplemented features), with the crash rate. The crash files themselves are saved 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":"#!ek9\ndefines 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 an error code coverage grid, charts and crash counts generated by -fuzz6.","keywords":["code","crash","dashboard","error","fuzz","heatmap","html","phase","report","statistics","throughput","visualisation"],"primaryTopics":["fuzz dashboard","fuzz html","fuzz statistics"],"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 +, * becomes /, / becomes *.\n- CMP_FLIP: == and <> swap, < and <= swap, > and >= swap, and < and > swap.\n- BOOL_LOGIC: true and false swap, and and or swap, contains and not contains swap, matches and not matches swap.\n- BOUNDARY: an integer literal is shifted up or down by one.\n- GUARD: the assign-if-unset :=? becomes a plain := (assign only if unset becomes assign always).\n- COLLECTION: .empty() becomes .length().\n- RETURN: a string literal on a <- line is replaced with \"mutated\".\n- METHOD_REMOVE: a method call statement is removed.\n- PURITY: a purity violation probe, reserved for purity checking rather than picked at random.\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":"#!ek9\ndefines 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 change <= 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 dev/generated.ek9\nThis reads source.ek9, harvests all functions and types, generates candidate test programs, compiles each to verify correctness, and writes the survivors to dev/generated.ek9. The output path must be a dev source (a dev/ directory, or a QA####_dev_ file): @Test programs and assert only compile there, so any other output path is refused with E81015.\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. It is scaffolding: each test calls one function or method with named edge-case arguments (EDGE_0 and so on) and carries a PLACEHOLDER assert such as 'assert isPrime(candidate: EDGE_0)?' plus an //AI: note. The author sets each expectation from the requirement - keep the assert, or flip it to 'assert not ...?' - never from what the code happens to return, because keeping whichever version passes would cement bugs as green tests.\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":"#!ek9\ndefines 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":["-fuzztest","generate tests"],"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":"#!ek9\ndefines 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 unset and empty strings.\n      The guard skips an UNSET string (an empty \"\" is set,\n      and its first() is simply unset). Generated tests with\n      either input compile and exercise the function.\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      unsetText <- String()\n      emptyText <- \"\"\n      stdout.println(`first of Hello: ${safeFirstChar(greeting)}`)\n\n      //An unset Character would print nothing, so print whether it is set\n      stdout.println(`first of unset is set: ${safeFirstChar(unsetText)?}`)\n      stdout.println(`first of empty is set: ${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":["compile-check-discard","generated tests"],"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\nCoverage must reach 80% to package with -P.\n3. Generate tests: fill coverage gaps automatically.\n  ek9 -fuzztest source.ek9 generated.ek9\nReview generated tests, keep the useful ones.\n4. Mutation testing: assess test quality.\n  ek9 -fuzzmutate source.ek9 mutations/ -test tests.ek9\nSurviving mutants show where tests are too weak.\n5. Compiler fuzzing: verify robustness.\n  ek9 -fuzz 60\nEnsures 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":"#!ek9\ndefines 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":["fuzz coverage","fuzz mutation"],"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 full generated source is saved here (it is not minimised) together with a .log file holding the stack trace. Each name is made of a prefix, a timestamp and a counter, for example compiler-crash-20261004-101500-1.ek9 beside compiler-crash-20261004-101500-1.log.\n\nGENERATED TESTS (-fuzztest)\n  ek9 -fuzztest source.ek9 dev/generated.ek9\nThe second argument is the output file path, and it must be a dev source (a dev/ directory or a QA####_dev_ file) or it is refused with E81015. 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 its mutation category and location.\n- mutation_<nnn>_<category>_L<line>.ek9, for example mutation_001_bool_logic_L70.ek9, are the 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 dev/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":"#!ek9\ndefines 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":["fuzz-crashes","crash files"],"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":"#!ek9\ndefines 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":["AFL","libFuzzer","source-level fuzzing"],"typicalErrors":[],"companions":[]}
{"id":759,"category":"Control Flow","question":"How do I assign a value and get a Boolean saying whether the new value is set?","url":"https://ek9.io/qa/QA0759.html","alternatePhrasings":["Can an assignment return a Boolean in EK9?","Is there a ?= operator in EK9?","How do I assign and then check isSet in one place?"],"answer":"In EK9 an assignment never yields a value, so the assignment and the set-check are two steps: assign with ':=', then test the target with '?'. ':=' ALWAYS evaluates the right-hand side and ALWAYS stores it - an unset result included - so afterwards the target holds exactly what was returned and 'name?' says whether it is SET. (EK9 has no '?=' operator; it was removed so that no assignment yields a value.)\n\nASSIGN, THEN CHECK\n  name as String?\n  name := getValue()\n  guardResult <- name?\nHere guardResult is a Boolean: true if getValue() returned a set value, false otherwise. Either way name now holds what getValue() returned, so it is initialised and safe to use.\n\nWHEN THE RHS IS UNSET\nThe unset value is still stored - it OVERWRITES a set target - and the check is false:\n  existing <- \"already here\"\n  existing := getEmpty()\n  result <- existing?\n  // result is false, existing is now UNSET\n\nTO KEEP AN EXISTING VALUE\nIf the old value must survive a missing new one, coalesce: 'existing := getEmpty() ?: existing', or fill only an unset target with ':=?' (assign-if-unset).\n\nIN A CONTROL-FLOW HEAD\nThe same two steps fit in one head: 'if name := f() then name?' assigns, then runs the body only if name is now set.\n\nSee Q74 for guard variables in if. See Q77 for guard variables in while. See Q79 for the assign-if-unset :=? operator. See Q1024 for re-point-and-check in control flow.","ek9Example":"#!ek9\ndefines 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      // === ASSIGN A SET VALUE, THEN CHECK ===\n\n      name as String?\n      name := getValue()\n      guardResult <- name?\n      stdout.println(guardResult)\n      stdout.println(name)\n\n      // === ASSIGN AN UNSET VALUE - it overwrites existing ===\n\n      existing <- \"already here\"\n      existing := getEmpty()\n      guardResult2 <- existing?\n      stdout.println(guardResult2)\n      stdout.println(existing?)\n\n      stdout.println(\"Done\")","migrationContext":"Java: 'name = getValue(); boolean assigned = name != null;'. Kotlin: 'name = getValue(); val ok = name != null'. Python: 'name = getValue(); ok = name is not None'. EK9: 'name := getValue()' then 'ok <- name?' - the same two steps, and name is never a null afterwards because an unset value is still an object.","keywords":["assignment","boolean","check","conditional","expression","guard","isset","null-safe","operator","result","return","safe","source","standalone"],"primaryTopics":[],"typicalErrors":[{"error":"E50030","correct":"guardResult <- name?","incorrect":"guardResult as String: name?","explanation":"'name?' is 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 assign-if-unset :=? 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 assign-if-unset work on config.host?","What does object.field :=? value do in EK9?"],"answer":"The assign-if-unset 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 assign-if-unset 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 assign-if-unset on local variables. See Q22 for variable declaration. See Q104 for uninitialised properties in classes.","ek9Example":"#!ek9\ndefines 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 assign-if-unset 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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?\n        firstSixSet <- d1? and d2? and d3? and d4? and d5? and d6?\n        rtn: firstSixSet 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?","What is the maximum class inheritance depth in EK9?","What triggers E11019 excessive inheritance depth?","How deep can class hierarchies be?","What are the DIT thresholds for each EK9 construct type?","What is the maximum inheritance depth in EK9?","How does E11019 work?","Why can I only extend 4 levels deep in EK9?","How does EK9 limit inheritance depth and what happens when exceeded?","How deep can class hierarchies be in EK9?","Is there a maximum inheritance depth?","Why does EK9 limit inheritance chain length?"],"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- Basili, Briand and Melo (IEEE TSE 1996): classes deeper in the inheritance tree (Chidamber and Kemerer's DIT) were more likely to be fault-prone\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.","ek9Example":"#!ek9\ndefines 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":["AWT","DIT","E11019","Swing","base","boundary","chain","circular","class","clean-code","composition","delegation","depth","design","extends","fragile","hierarchy","inheritance","limit","metric","migrate","open","quality","trait","type"],"primaryTopics":["inheritance depth","DIT","class hierarchy","code quality","E11019","DIT limit","inheritance depth limit","fragile base class"],"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 edit distance check for names?"],"answer":"EK9 checks for names of the same type, in reach of each other, that differ by a single character inserted, removed or changed (anywhere, the first and last positions included), by two adjacent characters swapped ('title' and 'titel' - but not the first two letters: 'latitude' and 'altitude' are allowed), or by case alone ('total' and 'Total', 'userID' and 'userid') - a weighted distance of 2 or less, where one character edit or one swap costs 2 and a case change costs 1; names that differ only in case are always reported. Locals are compared with locals, parameters with parameters, both with the properties of their type, and a type's properties with each other. 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 two or more character edits are fine ('counter' and 'count', 'account' and 'count', 'totalCount' and 'totalCoxyt')\n- Names shorter than 3 characters are not checked\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 far more than one character edit, so they are clearly distinguishable. Using 'totalCount' and 'totalCoumt' (one character substituted) 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":"#!ek9\ndefines 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 (far more than one character edit apart).\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: same-typed names a single character edit apart in one scope 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' (the compiler reports 'distance 2', its weighted score for one character edit). 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 guards 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":"#!ek9\ndefines 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":"The incorrect version adds an if, a for and an if at depths 3 to 5 (+3, +4 and +5, a gain of 12), taking the function from 29 to 41 and 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":"#!ek9\ndefines 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 dev source files (a dev directory or a name like QA0767_dev_tests.ek9); an assert in a non-dev file is E81015. In a dev file it must be reachable from a @Test program (otherwise E81011) and must not be reachable from a production program (E81012).\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":"#!ek9\ndefines 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 here, E81015 would trigger\n      because assert is only allowed in dev source files.\n    -?>\n    validateInput()\n      -> input as Integer\n      <- valid as Boolean?\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, E81015 would fire\n      (this is not a dev source file).\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 (or function) must be declared 'as abstract'; 'as open' alone is rejected with E50040\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":"#!ek9\ndefines 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":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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 as List of Any: [1, \"hello\", 3.14]\n      for item in items\n        result <- processor.handle(item)\n        stdout.println(result)\n\n      describer <- Describer()\n      entries as List of Any: [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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 extends ParentClass\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 ShowCalculation program captures the result of Calculator.add, a pure method. The mutation removes the capture, leaving a bare pure-method call that discards its result. That is E11051, the method-call sibling of E11050 (which is raised for a discarded operator call such as 'price.+(tax)'); the principle is the same: a pure call whose result is thrown away is dead code.\n\nSee Q238 for operator overview. See Q239 for comparison operators. See Q310 for code quality checks.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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 declare a return (omitting it is E07400); the return type is the author's choice\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 declares its sameness, <=> (returning Integer); <, ==, <> and the rest with another Temperature are generated from it and are never written by hand (E07181). It also defines < and == against an unrelated type (Float), each with a Boolean return declaration. 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":"#!ek9\ndefines 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      //The sameness: <, <=, ==, <>, >, >= with another Temperature are generated from it\n      operator <=> as pure\n        -> other as Temperature\n        <- rtn as Integer: degrees <=> other.degrees\n\n      //Comparisons with an unrelated type (Float) are written by hand\n      operator < as pure\n        -> threshold as Float\n        <- rtn as Boolean: degrees < threshold\n\n      operator == as pure\n        -> threshold as Float\n        <- rtn as Boolean: degrees == threshold\n\n      //Arithmetic operators must declare a return; the type is the author's choice\n      operator + as pure\n        -> delta as Float\n        <- rtn as Float: degrees + delta\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        -> threshold as Float\n        <- rtn as Boolean: degrees < threshold","incorrect":"      operator < as pure\n        -> threshold as Float","explanation":"The < operator must return a Boolean but the return declaration is missing. (Here '<' compares with an unrelated type, Float; '<' with another Temperature is generated from '<=>' and is never written by hand - E07181.) Comparison operators have a specific return type requirement enforced by the compiler. See ek9 -h E07520 for details."},{"error":"E07400","correct":"      operator + as pure\n        -> delta as Float\n        <- rtn as Float: degrees + delta","incorrect":"      operator + as pure\n        -> delta as Float","explanation":"The + operator must declare a return, but the return declaration is missing. Arithmetic operators have no fixed return type - the type is your choice - but they must return something. See ek9 -h E07400 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        -> threshold as Float\n        <- rtn as Boolean: degrees < threshold","incorrect":"      operator < as pure\n        ->\n          threshold as Float\n          extra as Integer\n        <- rtn as Boolean: degrees < threshold","explanation":"The < operator takes exactly one parameter but two were provided. Comparison operators must accept exactly one argument. See ek9 -h E06280 for details."},{"error":"E06290","correct":"      operator == as pure\n        -> threshold as Float\n        <- rtn as Boolean: degrees == threshold","incorrect":"      operator == as pure\n        <- rtn as Boolean: true","explanation":"The == operator requires one parameter but none was provided. Comparison operators must accept exactly one argument. 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 the increment, which surprises many readers, and ++x gives the NEW value. Both forms 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 <- Integer(count)\n  count++\n  // Now oldCount has the previous value (Integer(count) made a copy; plain oldCount <- count would make oldCount an alias of the same object, which count++ then changes)\n\nSee Q238 for operator overview. See Q239 for comparison operators.","ek9Example":"#!ek9\ndefines 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 error do I get when I call a function with too many arguments?","What error do I get when I call a constructor with 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\nERRORS\nCalling a function with the wrong number of arguments is E06270 (parameter mismatch). Calling a method or constructor with the wrong number of arguments is E50060 (not resolved).\n\nTHIS EXAMPLE\nThe Pair class constructor takes exactly two arguments. The plus() function takes exactly two Integer 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":"#!ek9\ndefines 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 function\n\n    plus() as pure\n      ->\n        a as Integer\n        b as Integer\n      <- rtn as Integer: a + b\n\n  defines program\n\n    ShowPair()\n      stdout <- Stdout()\n      pair <- Pair(\"hello\", 42)\n      if pair?\n        stdout.println(pair.describe())\n\n      first <- 20\n      second <- 22\n      total <- plus(first, second)\n      stdout.println(`Total: ${total}`)","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":["E06270","E50060","arguments","constructor","count","function","mismatch","parameters","too few","too many"],"primaryTopics":["argument count","E06270","E50060"],"typicalErrors":[{"error":"E06270","correct":"      total <- plus(first, second)","incorrect":"      total <- plus(first, second, first)","explanation":"The plus function requires exactly two arguments but three were supplied, so the call is a parameter mismatch. Remove the extra argument. See ek9 -h E06270 for details."},{"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":"#!ek9\ndefines 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":"#!ek9\ndefines 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?","What does 'closed by default' mean in EK9?","What is the 'as open' modifier in EK9?","Why does E05030 say 'not open to be extended'?"],"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  defines class\n    Shape as open         // Now it can be extended\n      name <- String()\n      default operator ?\n    Base as abstract      // Abstract classes are implicitly open\n      describe() as abstract\n        <- rtn as String?\n\nCOMPOSITION ALTERNATIVE\nInstead of extending a closed class, wrap it as a field:\n  ItemHolder\n    items <- List() of String\n    addItem()\n      -> 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":"#!ek9\ndefines 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","Kotlin","closed","composition","default","extend","extends","final","inherit","inheritance","open","sealed"],"primaryTopics":["closed by default","E05030","inheritance design","as open","composition over inheritance","cant extend"],"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":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 Q145 for why and Q144 for 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 Q145 for why there is no break, continue or return. See Q144 for the alternatives.","ek9Example":"#!ek9\ndefines 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":"What does 'this' mean in a function in EK9, and what triggers E05070?","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":"In a class, record or component, 'this' refers to the current object instance, so methods, operators and constructors can write this.count.\n\nTHIS IN A FUNCTION\n'this' is also valid in a function, but there it means the function object itself, not an instance with fields. So 'me <- this' compiles in a function, while 'this.count' in a function is E50001 (not resolved): a function has no fields.\n\nWHAT IS ACTUALLY REJECTED\n- this() in a function: E05070 (inappropriate use of 'this'). A function has no constructors to delegate to.\n- this() or super() in a class method (not a constructor): E05060. Constructor delegation is only valid inside a constructor.\n- Direct assignment to this (this := Counter()): E05090. 'this' can be used with :=:, :~:, +=, -=, /= and *=, but not direct assignment.\n\nTHIS EXAMPLE\nThe Counter class uses this.count in its methods - valid because methods run on an instance with that field. The standalone increment() function has no fields, so it works from its parameter instead.\n\nSee Q50 for functions vs methods. See Q93 for class basics.","ek9Example":"#!ek9\ndefines 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: in class/record/component members this is the instance; in a function this is the function object (no fields, so this.field is E50001); this() outside a constructor is E05070 (function) or E05060 (method).","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 (also requires default, E07320)\n  switch category\n    case \"A\"\n      stdout.println(\"Alpha\")\n    default\n      stdout.println(\"Other\")\n\nENUMERATED TYPES: NO EXCEPTION\nA switch on an enumeration needs a default too, even when its cases list every value: the value switched on may be UNSET, and the default is what handles that (E07320 for a statement, E07330 for an expression). An enumeration switch must ALSO cover every value with its cases (E07310), so the default cannot stand in for a forgotten value.\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":"#!ek9\ndefines 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: every switch, statement or expression, enum or not, requires a default (E07320/E07330), because the value may be unset; an enum switch must also cover every value (E07310).","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 Q632 for declaration order. See Q22 for variable declarations.","ek9Example":"#!ek9\ndefines 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 ':=' (the declared return variable is the exception). 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- Assign-if-unset: 'value :=? fallback' (only assigns if unset)\n- Return variable initialisation: '<- rtn as Type: expression'\n- Assigning to the declared return variable: 'rtn: value' (the returning variable is exempt from E08100)\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 total with '<-' and gives the value to its declared return variable with 'rtn: total'. No other variable is reassigned.\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":"#!ek9\ndefines 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":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":"#!ek9\ndefines 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 Q29 for tri-state semantics.","ek9Example":"#!ek9\ndefines 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 a few must come AFTER (suffix). Each works only on types that define it.\n\nPREFIX OPERATORS (must come BEFORE)\n  - $value       converts to String\n  - $$value      converts to JSON\n  - #? value     hashcode (NOT a length - use 'length' for that)\n  - #< text      first element, e.g. first Character of a String (types that define #<, such as String and Bits; List does not)\n  - #> text      last element (types that define #>, such as String and Bits)\n  - ~ list       reverses a List; ~ on Bits or Boolean negates (Integer has no ~)\n  - not active   boolean negation\n  - abs number   absolute value\n  - sqrt number  square root\n  - length name  gets length\n\nEmptiness is not a prefix operator: write 'list is empty' (infix).\n\nSUFFIX OPERATORS (must come AFTER)\n  - value?       checks if value is set (isSet)\n  - number!      factorial (an expression, like '?')\n  - counter++    increment, and counter-- decrement (statements only - E07950 inside an expression)\n\nCOMMON MISTAKES\n  text#<       WRONG: #< is prefix, use '#< text'\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":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 (Juergens et al., ICSE 2009) found inconsistent changes to duplicated code very frequent - only some instances get updated - and every second unintentional one a fault.\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  // ERROR E11065: 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 Q816 for boolean argument naming. See Q777 for discarded returns.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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 does E01094 'close' is not a statement mean?","Why must I use try-with-resources for local variables?","How do I properly close resources in EK9?","Can I write 'close conn' when I have finished with a connection?"],"answer":"EK9 has no 'close' statement: 'close conn' is E01094, rejected as early as 'break' or 'return'. Every close is generated, because a close written by hand is unsafe in two ways. If an exception happens before it, the resource leaks:\n\n  conn <- DatabaseConn(\"test-db\")\n  doWork()       //an exception here leaks conn\n\nA manual close() is skipped whenever an exception is thrown before it is reached. And if the value has another owner - the caller that passed it, the try that made it - a close by hand closes it twice, or under an owner still using it.\n\nCORRECT: TRY-WITH-RESOURCES\nA local closeable is declared in the try header, and the try closes it when it ends, normally or by exception:\n\n  try\n    -> conn <- DatabaseConn(\"test-db\")\n    stdout.println(conn.query(\"SELECT 1\"))\n\nWHERE THE OTHER CLOSES COME FROM\n- A closeable value you never name is closed by the statement that made it.\n- A type's own closeable properties are closed by its 'operator close' - 'default operator close', or the chain after a close you write for extra work.\n- A parameter, a capture or a returned value is closed by its owner, never by you.\nThe close operator is never called as a method either: 'conn.close()' is E81060.\n\nSee Q137 for try-with-resources syntax. See Q803 for abandoned resource detection. See Q1600 for who owns a closeable.","ek9Example":"#!ek9\ndefines 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: there is no close statement (E01094) and close() is never called as a method (E81060) - every close is generated.","keywords":["E01094","close","leak","local","manual","resource","resources","try","with"],"primaryTopics":["manual close","E01094","resource management"],"typicalErrors":[{"error":"E01094","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":"'close' is not a statement in EK9: a close written by hand never runs if an exception comes first, and closes twice a value somebody else owns. Declare the local in the try header and the try closes it. See ek9 -h E01094 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 closeable held in a named local?","How does EK9 detect abandoned resources?","Why must a named closeable be a try-with-resources resource?"],"answer":"EK9 raises E81056 when a local variable holds a closeable value (its type has an 'operator close') outside a try-with-resources declaration. Nothing would ever close it, so it is a guaranteed resource leak.\n\nTHE RULE\nA closeable value you never NAME is owned by the statement that created it and is closed when that statement finishes. A closeable value you NAME must be declared as a try-with-resources resource, and is closed when the try ends.\n\nNOT A LOCAL\n- RETURNED: the returning variable hands it to the caller (a factory)\n- A PROPERTY: the object owns it\n- A PARAMETER: the caller owns it\nPassing a named local on to a function or constructor does NOT transfer it - the local still holds it.\n\nFIX OPTIONS\n1. try-with-resources: closed when the try ends (recommended)\n2. Do not name it: FileConn(\"log.txt\").readAll() is closed when its statement finishes\n3. Return it: transfer ownership to the caller\n\nA resource nobody closes stays open until the process ends - the simplest leak of all.\n\nSee Q1520 for the whole ownership rule. See Q802 for manual close rejection. See Q137 for try-with-resources syntax.","ek9Example":"#!ek9\ndefines 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: an unnamed closeable is closed by its statement and a named one must be a try-with-resources resource (compile-time error E81056), so abandoned resources cannot exist.","keywords":["E81056","abandoned","close","leak","ownership","resource","transfer","try","try-with-resources"],"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 value held in a named local variable must be a try-with-resources resource, or nothing would ever close it. Declare it in the try. 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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()\n","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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. Test doubles are ordinary implementations of a contract, chosen either by the caller or by dependency injection.\n\nTHE PATTERN\n1. Define the behaviour as an abstract contract\n2. Production code depends on the contract, not the concrete implementation\n3. In tests, provide a test implementation of the contract\n4. For injected dependencies, an application block selects which implementation is registered\n\nWHY NO MOCKING FRAMEWORK\nMocking frameworks exist because languages allow tight coupling to concrete classes. In EK9:\n- Code depends on traits or abstract components, not concrete types\n- The DI system wires implementations at compile time\n- Test implementations are just another implementation of the contract\n- No reflection, no bytecode manipulation, no proxy objects\n\nTRAIT-BASED TEST DOUBLES (passed directly)\nA class implements a trait with 'with trait of':\n  defines trait\n    Formatter\n      format() as pure abstract\n        -> number as Integer\n        <- rtn as String?\n\n  defines class\n    SimpleFormatter with trait of Formatter\n      override format() as pure\n        -> number as Integer\n        <- rtn as String: $number\nAny code that takes a Formatter can be handed the test double.\n\nINJECTED TEST DOUBLES (components)\nOnly components can be registered and injected - registering a class, or registering anything 'as' a trait, is E50020. So the injected contract is an abstract component, the test double is wired in an application, and the program runs with that application:\n  defines component\n    DataStore as abstract\n      fetch() as abstract\n        -> key as String\n        <- rtn as String?\n\n      default operator ?\n\n    FakeDataStore extends DataStore\n      override fetch()\n        -> key as String\n        <- rtn as String: \"test-value\"\n\n      default operator ?\n\n  defines application\n    TestApp\n      register FakeDataStore() as DataStore\n\n  defines program\n    MockingDemo() with application of TestApp\n      stdout <- Stdout()\n      store as DataStore!\n      stdout.println(store.fetch(\"user\"))\n\nThe program prints test-value. A ProductionApp registers RealDataStore() as DataStore instead; the program code does not change.\n\nADVANTAGES\n- Compile-time verified: missing method implementations caught immediately\n- No magic: test doubles are normal classes and components\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":"#!ek9\ndefines 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 component\n\n    // === INJECTED CONTRACT: an abstract component, not a trait ===\n\n    DataStore as abstract\n      fetch() as abstract\n        -> key as String\n        <- rtn as String?\n\n      default operator ?\n\n    RealDataStore extends DataStore\n      override fetch()\n        -> key as String\n        <- rtn as String: `db-value-for-${key}`\n\n      default operator ?\n\n    FakeDataStore extends DataStore\n      override fetch()\n        -> key as String\n        <- rtn as String: \"test-value\"\n\n      default operator ?\n\n  defines application\n\n    // Production wiring\n    ProductionApp\n      register RealDataStore() as DataStore\n\n    // Test wiring: the fake replaces the real store\n    TestApp\n      register FakeDataStore() as DataStore\n\n  defines program\n\n    MockingDemo() with application of TestApp\n      stdout <- Stdout()\n\n      // Trait doubles: pass whichever implementation you want\n      fancy <- FancyFormatter()\n      stdout.println(fancy.format(42))\n\n      simple <- SimpleFormatter()\n      stdout.println(simple.format(42))\n\n      // DI doubles: TestApp injects FakeDataStore\n      store as DataStore!\n      stdout.println(store.fetch(\"user\"))\n\n      stdout.println(\"No Mockito needed\")\n      stdout.println(\"Traits and components 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 passed directly, or abstract components with an application that registers the fake - no mocking library needed.","keywords":["application","component","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":"#!ek9\ndefines 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":"    compute() as pure\n      -> n as Integer\n      <- rtn as Integer: n * n","incorrect":"    compute() as pure\n      -> n as Integer\n      <- rtn as Integer: n * n\n\n    verifyResult()\n      -> candidate as Integer\n      assert candidate > 0","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":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%\nEK9 sets the gate at 80% because:\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\nWHAT IS RECORDED\nCoverage records whether each line was executed; there is no flag that selects another counting mode.\n\nVIEWING COVERAGE\n  ek9 -t4 myApp.ek9    detailed coverage report that lists the uncovered items\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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      -> count as Integer\n      <- rtn as Integer: count\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\n        > stdout","incorrect":"| head AcceptsArgument\n        > stdout","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. The variable must be named in the capture list, here (capturedValue), or the body cannot see it:\n  supplier <- (capturedValue) 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":"#!ek9\ndefines 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.\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":"#!ek9\ndefines 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                  limit <- 9\n                  if inputValue < limit\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.==(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.==(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":"#!ek9\ndefines 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?","Why does EK9 require named arguments when passing multiple Boolean literals?","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":"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":"#!ek9\ndefines 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","argument","arguments","boolean","call","named","parameters","positional","quality","readability","site"],"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":"#!ek9\ndefines 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 <- 10\n  stdout.println($(amount - amount))       // ERROR E08083 always 0\n  stdout.println($(amount / amount))       // ERROR E08083 always 1 (or division by zero if 0)\n  stdout.println($(amount mod amount))     // ERROR E08083 always 0\n  stdout.println($(amount rem amount))     // ERROR E08083 always 0\n  stdout.println($(amount mod 1))          // ERROR E08083 always 0\n  stdout.println($(amount rem 1))          // ERROR E08083 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":"#!ek9\ndefines 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 as open\n    name as String: \"base\"\n  ChildRecord extends BaseRecord\n    name as String: \"child\"    // ERROR: E02010 duplicate property\nThe same rule applies to a class or a component that redeclares a property of its super.\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":"#!ek9\ndefines module qa.quality.duplicate.property\n\n  defines record\n\n    BaseRecord as open\n      label as String: \"base\"\n      default operator ?\n\n    ChildRecord extends BaseRecord\n      childLabel as String: \"child\"\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: \"child\"","incorrect":"    ChildRecord extends BaseRecord\n      label as String: \"child\"","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?","Why does EK9 flag checking empty on a freshly constructed list?","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 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":"#!ek9\ndefines 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","data","dead","dict","empty","flow","list","redundant","tautology"],"primaryTopics":["redundant empty check","E08092","E08093","REDUNDANT_EMPTY_CHECK"],"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 E50080 when I construct an abstract class?","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:\n  defines module demo.shapes\n    defines class\n      Shape as abstract\n        default operator ?\n    defines program\n      Demo()\n        s <- Shape()   // ERROR: E50080\nConstructing an abstract generic or abstract function type is a related but different error (E10030); see Q841.\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":"#!ek9\ndefines 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":["E50080","abstract","concrete","construction","constructor","implementation","type"],"primaryTopics":["abstract construction","E50080"],"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 a parent method, as in super.methodName(), and for calling the parent constructor from inside a constructor, as in super(name).\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 and constructor chaining.\n\nSee Q96 for inheritance. See Q97 for class vs record.","ek9Example":"#!ek9\ndefines 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 parent method calls and parent constructor calls.","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 E07410: must return MyNumber\n\nOTHER OPERATOR RULES\n- A type's sameness is either == (must return Boolean) or <=> (the spaceship, must return Integer)\n- The other comparisons of a type with itself (<>, <, <=, >, >=) are generated from that sameness and must not be written (E07181)\n- A comparison taking a different, unrelated argument type may still be written and returns Boolean\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":"#!ek9\ndefines 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":"Most EK9 stream operators, such as filter, map and uniq, require a function with exactly one parameter matching the stream element type. The exception is 'sort by', whose comparator takes TWO parameters of the element type and returns an Integer.\n\nTHE RULE\nA filter predicate has a single parameter:\n  isPositive() as pure\n    -> amount as Integer       // ONE parameter: correct for filter\n    <- rtn as Boolean: amount > 0\n\nA function with two parameters cannot be used by filter, map or uniq:\n  isInRange() as pure\n    ->\n      lo as Integer            // TWO parameters: wrong for filter\n      hi as Integer\n    <- rtn as Boolean: lo < hi\n\nTHE CODES FOR A WRONG PARAMETER COUNT\n- filter or map with 0 or 2+ parameters: E06300\n- uniq with 0 or 2+ parameters: E07460\n- sort by with 1 parameter: E07470\n- sort by with 2 parameters but a non-Integer return: E07550\n\nWHY THIS RULE\nStream operators call the function once per element (or once per pair of elements for sort), passing the current element as the argument. A function with a different number of parameters cannot receive it.\n\nHOW TO FIX\n- Use a single parameter matching the stream type (two for a sort comparator)\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":"#!ek9\ndefines 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), sorted() takes a two-argument Comparator. 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 the parameter count the operator needs: one for filter, map and uniq, two for a sort comparator.","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"A parameter in EK9 is always PRESENT: the caller must pass something for it. So declaring a parameter with ? is not needed, and the compiler rejects it.\n\nTHE RULE\nParameters cannot use the ? modifier:\n  process()\n    -> input as String?    // ERROR: E07300\n    <- rtn as String: input\n\nTHE FIX\nRemove the ? modifier:\n  process()\n    -> input as String     // CORRECT\n    <- rtn as String: input\n\nPRESENT IS NOT THE SAME AS SET\nE07300 is about the declaration, not the value. A caller may still pass an UNSET value - processInput(String()) runs the body with 'input' present but unset, and here the result comes back unset too (the program below prints 'Result set: false'). So the body must still treat a parameter as possibly unset, with a guard or the ? operator, when that matters.\n\nWHY THIS MATTERS\nEK9 pushes for everything to be initialised. A parameter can never be absent, so '?' on its declaration would be false optionality - the possibly-unset case is handled where the value is used.\n\nSee Q310 for code quality. See Q126 for function patterns.","ek9Example":"#!ek9\ndefines 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)\n\n      //A caller CAN pass an unset value: the parameter is present but unset\n      missing <- String()\n      fromUnset <- processInput(missing)\n      stdout.println(`Result set: ${fromUnset?}`)","migrationContext":"Java: any reference parameter may be null; @Nullable/@NonNull are unenforced annotations. Kotlin: parameters can be nullable with ?. Rust: parameters are always initialised. Go: parameters always have zero values. EK9: parameters are always present — ? modifier not allowed (E07300) — but a caller may pass an unset value.","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, so they are never absent and the ? modifier is not allowed. Remove it - a caller may still pass an unset value, so check with ? or a guard where it matters. 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    -> rawInput as sanitized String    // CORRECT: callee declares sanitization\n    <- rtn as String: rawInput\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":"#!ek9\ndefines 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":["Is a duplicated record property a JSON error or a duplicate property error?","Why can't my child record redeclare a parent property with default operators?","How does property duplication affect JSON serialization in EK9?"],"answer":"EK9 requires every property name to be unique across a record's inheritance hierarchy, whatever operators it defaults. A child record that redeclares a parent property is E02010, with or without 'default operator'.\n\nTHE RULE\n  defines record\n    ParentRecord as open\n      name as String: String()\n      default operator\n    ChildRecord extends ParentRecord\n      name as String: String()       // ERROR E02010: property duplicated\n      default operator\n\nWHY IT ALSO PROTECTS JSON\nThe $$ operator generates JSON like {\"name\": \"value\"}. Because the name can never appear twice in the hierarchy, a generated $$ never has duplicate keys, so there is no separate JSON error. Writing $$ by hand does not make the redeclaration legal.\n\nHOW TO FIX\n- Use a different name in the child: childName as String\n- Or set the inherited property instead of redeclaring it\n\nSee Q819 for duplicate property basics. See Q97 for records.","ek9Example":"#!ek9\ndefines 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":["E02010","duplicate","json","property","record","serialization","toJson"],"primaryTopics":["duplicate JSON property","E02010"],"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:\nclass, record, component, trait, service, enumeration\n\nNON-AGGREGATE TYPES\nCallable units without properties:\nfunction, 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 E01040 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 'first <=> second' (comparator), those become requirements on the parameterizing type. Functions cannot satisfy constructor or comparator requirements, so parameterizing such generics with a function is rejected: using 'T()' triggers E06110, and using any operator other than '?' (such as '<=>') triggers E06120.\n\nIMPLICIT CONTRACT DISCOVERY\nWhen you write:\n  Holder of type T\n    createNew()\n      <- rtn as T: T()      // Requires T has a constructor\n    compare()\n      ->\n        a as T\n        b as T\n      <- 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 'first?' and 'second?' 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":"#!ek9\ndefines 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      -> amount as Integer\n      <- rtn as Boolean?\n\n    IsPositive() is CheckFn\n      -> amount as Integer\n      <- rtn as Boolean: amount > 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        -> item as T\n\n      check()\n        ->\n          first as T\n          second as T\n        <-\n          rtn as Boolean: first? and second?\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        -> item as T\n      check()\n        ->\n          first as T\n          second as T\n        <-\n          rtn as Boolean: first? and second?\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        -> item as T\n      check()\n        ->\n          first as T\n          second as T\n        <-\n          rtn as Boolean: first? and second?\n      compare()\n        ->\n          first as T\n          second as T\n        <-\n          rtn as Integer: first <=> second\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 'first <=> second' 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 <=> that ignores the delegate, with a hand-written #? hashing the same fields\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":"#!ek9\ndefines 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        -> other as EventHandler\n        <- rtn as Integer: eventName <=> other.eventName\n\n      //A hand-written <=> needs a hand-written #? hashing what it compares\n      operator #? as pure\n        <- rtn as Integer: #?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        -> other as SimpleRecord\n        <- rtn as Integer: label <=> other.label\n\n      operator #? as pure\n        <- rtn as Integer: #?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        -> other as EventHandler\n        <- rtn as Integer: eventName <=> other.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\nSAMENESS AND GENERATED COMPARISONS\nA type declares its sameness once: '<=>' (or '==' for a type that cannot be ordered). The comparisons ==, <>, <, <=, >, >= are then ALWAYS generated from it - never written or requested ('default operator >=' is E07181). A hand-written '<=>' needs a hand-written '#?' hashing the same fields (E07184). You can provide '<=>' and '#?' explicitly and default the rest.\n\nTHIS EXAMPLE\nThe Shape record provides an explicit comparator and hashcode, then uses 'default operator' to auto-generate the remaining supported operators (such as $ and ?); >=, <=, >, <, == and <> come from its '<=>'.\n\nSee Q116 for default operator overview. See Q96 for operator semantics. See Q774 for abstract constructor.","ek9Example":"#!ek9\ndefines 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 — the sameness: >=, <=, ==, <> etc. are generated from it\n      operator <=> as pure\n        -> other as Shape\n        <- rtn as Integer: sides <=> other.sides\n\n      //A hand-written <=> needs a hand-written #? hashing the same field\n      operator #? as pure\n        <- rtn as Integer: #?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        -> other as Config\n        <- rtn as Integer: priority <=> other.priority\n\n      operator #? as pure\n        <- rtn as Integer: #?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        -> other 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":"A switch pre-flow written WITHOUT a separate control expression must assign to a simple variable, and that variable becomes the subject of the switch ('switch result += 3' switches on result). A compound assignment on a property access such as 'scoring.prop1 += 3' has no variable to be the subject, so the compiler reports E07340 ('failed to find subject of flow'). Declare a variable and name it as the control: 'switch controlScore <- scoring.prop1 + 3 with controlScore'.\n\nSee Q69 for multi-case switch. See Q144 for no break/continue/return.","ek9Example":"#!ek9\ndefines 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: a pre-flow with no separate control must assign to a simple variable (the subject); property targets need a declaration plus 'with'.","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":"A compound assignment on a property in switch pre-flow gives the switch no variable subject (E07340). Declare a variable and name it with 'with' 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)?","Can I guard a stream pipeline expression?"],"answer":"Yes. A body-skipping guard ('<-' declaration, ':=?' 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. 'temperature' starts uninitialised, so the guard fills it, the switch runs, and 'resultText' gets a case value; had the guard left 'temperature' unset, 'resultText' would be the initial \"Unknown\".\n\nNOT THE STREAM FORM\nThis holds for switch/for/while/do-while/try, which all declare a returning param. A STREAM expression does not: it ends in '| collect as T', so there is nowhere to write the initial value that would be the result on the skip path. A guarded stream expression is therefore rejected with E08056:\n  collected <- for limit <- getLimit() then i in 1 ... 3 | collect as List of Integer  //E08056\nThe stream STATEMENT form is fine with a guard - there is no result to leave undeclared. See Q1381.\n\nSee Q759 for guard patterns. See Q69 for multi-case switch. See Q1381 for guards on stream pipelines.","ek9Example":"#!ek9\ndefines 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","assign-if-unset","conditional","expression","guard","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":"A body-skipping guard ('<-', ':=?') can skip the switch body entirely, and on that path the result is the returning variable's INITIAL value. Declared uninitialised ('<- result as String?') there would be nothing to return on the skip path, so the initialiser is what makes a guarded switch EXPRESSION legal. The stream form of this trap is E08056 - see QA1381, which has an actual pipeline. See ek9 -h E08050 for details."},{"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\nThe program below is a plain program: it does not select an application, and it creates the Processor class and the Logger component directly with constructor calls, so no dependency injection takes place. It shows the placement the rule is about: a program is the only construct that may carry 'with application of <name>' (when it does, its injected '!' variables come from that application); the methods process() and log() carry no such clause and must not.\n\nSee Q111 for components. See Q227 for compile-time DI. See Q231 for program-application linking.","ek9Example":"#!ek9\ndefines 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 one does not need to). Methods such as process() and log() above never can.\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":"#!ek9\ndefines 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. Given a function that takes a String:\n  acceptsString()\n    -> text as String\n    <- rtn as String: text\n\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":"#!ek9\ndefines 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 pure function (r: \"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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":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?","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    <- response as HTTPResponse: ...\n\nSee Q657 for URI mapping. See Q659 for return type requirement.","ek9Example":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"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 Bytes: Bytes().withUtf8(`{\"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"],"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 Q215 for sanitized overview.","ek9Example":"#!ek9\ndefines 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":["sanitized modifier","E07920"],"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 and implement it with a concrete subtype:\n  defines function\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()\nThen assign the concrete version:\n  handler as Transformer: UpperTransformer\n\nINCORRECT PATTERN\n  defines function\n    Transformer as abstract\n      -> input as String\n      <- rtn as String?\n  defines program\n    AbstractDemo()\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":"#!ek9\ndefines 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":"#!ek9\ndefines module qa.dispatcher.private.super\n\n  defines class\n\n    BaseHandler as open\n\n      protected process()\n        -> input as Integer\n        require input?\n\n      default operator ?\n\n    SubDispatcher extends BaseHandler\n\n      process() as dispatcher\n        -> input as Any\n        require input?\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        -> input as Integer\n        require input?","incorrect":"      private process()\n        -> input as Integer\n        require input?","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  defines module demo.sealed\n    defines trait\n      Workable allow only AbstractHelper, ConcreteWorker  //ERROR E05250\n        execute() as abstract\n          <- rtn as String?\n    defines class\n      AbstractHelper with trait of Workable as abstract\n        override execute()\n          <- rtn as String: \"abstract\"\n      ConcreteWorker with trait of Workable\n        override execute()\n          <- rtn as String: \"work\"\n\nSee Q298 for 'allow only' on traits. See Q300 for abstract classes and allow only. See Q608 for sealed classes versus sealed traits.","ek9Example":"#!ek9\ndefines 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":["trait allow only","E05250"],"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\nSee Q10 for module structure. See Q12 for module naming. See Q852 for reserved module names.","ek9Example":"#!ek9\ndefines 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":["reference qualifier","E01010","references module"],"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?","What module names are reserved in EK9?","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 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.math (E01020)\n\nThe rule is exact: only these two names are reserved. org.ek9.lang.custom, org.ek9.math.extra and other org.ek9 modules are ordinary module names.\n\nSee Q10 for module structure. See Q851 for reference syntax. See Q14 for package organization.","ek9Example":"#!ek9\ndefines 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","library","module","name","namespace","org.ek9","org.ek9.lang","org.ek9.math","reserved","standard","syntax"],"primaryTopics":["reserved module","E01020","reserved namespace"],"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":["Is reusing a parent field name a JSON error 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. In fact they must be unique in every hierarchy, with or without $$: a child that redeclares a parent's field is E02010 (DUPLICATE_PROPERTY_FIELD). Because of that rule a generated $$ can never meet a duplicate key, so there is no separate JSON error.\n\nWHY UNIQUE NAMES ARE REQUIRED\nJSON keys must be unique within an object. If a parent had field 'label' and a child also had field 'label', the JSON output would be ambiguous - and, more fundamentally, the object would hold two fields where the author meant one. EK9 rejects the redeclaration at compile time.\n\nCORRECT PATTERN\nUse unique field names across the hierarchy:\n  defines class\n    BaseConfig as open\n      configName <- \"default\"\n      default operator ?\n    ExtendedConfig extends BaseConfig\n      priority <- 0\n      default operator ?\n\nINCORRECT PATTERN\n  defines class\n    BaseConfig as open\n      label <- \"default\"\n      default operator ?\n    ExtendedConfig extends BaseConfig\n      label <- \"custom\"     // E02010: duplicate property\n      default operator ?\n\nSee Q200 for JSON basics. See Q202 for JSON operator patterns. See Q763 for inheritance depth.","ek9Example":"#!ek9\ndefines 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        -> other as BaseConfig\n        <- rtn as Integer: configName <=> other.configName\n\n      operator #? as pure\n        <- rtn as Integer: #? configName\n\n      default operator\n\n    <?-\n      Extended configuration with unique field name 'priority'.\n      Does NOT reuse 'configName', which would be E02010.\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      //Sameness (<=>) is inherited from BaseConfig and sealed; this gives ? $ $$ :=:\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: E02010 rejects a redeclared property name at compile time, so duplicate JSON keys cannot arise.","keywords":["E02010","JSON","ambiguous","compile-time","duplicate","field","hierarchy","operator","property","serialization"],"primaryTopics":["E02010","json duplicate","duplicate json property"],"typicalErrors":[{"error":"E02010","correct":"priority <- 0\n\n      ExtendedConfig()\n        ->\n          cn as String\n          p as Integer\n        super(cn)\n        priority :=: p","incorrect":"configName <- \"custom\"\n\n      ExtendedConfig()\n        ->\n          cn as String\n          p as Integer\n        super(cn)\n        configName :=: cn","explanation":"Parent BaseConfig already has a field named configName. The child cannot redeclare that name, whatever operators are defaulted. Use a unique field name in the child. See ek9 -h E02010 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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        -> other as Address\n        <- rtn as Integer: street <=> other.street\n\n      operator #? as pure\n        <- rtn as Integer: #? 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":"#!ek9\ndefines 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\n\n    ServiceCoordinator\n      svcA ServiceA: ImplA()","incorrect":"defines class\n\n    ServiceCoordinator\n      svcA ServiceA: ImplA()","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":"#!ek9\ndefines 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":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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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      default operator ?\n    PricerImpl with trait of PricingService\n      override calculatePrice()\n        require true\n      default operator ?\n    ShipperImpl with trait of ShippingService\n      override ship()\n        require true\n      default operator ?\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?","Why does EK9 limit injection fields per component to four?","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":"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\nTHE BOUNDARY\nExactly four injection fields compile; a fifth triggers E11040.\n\nSee Q227 for compile-time DI validation. See Q229 for circular deps. See Q325 for component patterns.","ek9Example":"#!ek9\ndefines 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","excessive","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":"#!ek9\ndefines 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(not active)\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 holding the opposite state (active is false)\n\nSee Q238 for operator overview. See Q839 for promote operator (which must return a DIFFERENT type).","ek9Example":"#!ek9\ndefines 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(not 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(not 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":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?","Why must functions used with stream call/async return a value?","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 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 Q813 for call/async rules.","ek9Example":"#!ek9\ndefines 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?","Why must functions used with stream call/async take no arguments?","What triggers E06310 REQUIRE_NO_ARGUMENTS?","Why can't I stream functions with parameters through 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 Q866 for the return requirement.","ek9Example":"#!ek9\ndefines 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","zero-arg"],"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  defines service\n\n    AccountService for :/accounts open\n\n      findById() as GET for :/{userId}\n        -> userId as Integer\n        <- response as HTTPResponse: (captured: userId) with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`${captured}`)\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-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\nInteger is a valid path parameter type.\n\nINCORRECT PATTERN\n  defines service\n\n    AccountService for :/accounts open\n\n      findById() as GET for :/{userId}\n        -> userId as HTTPRequest\n        <- response as HTTPResponse: (captured: userId) with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`${captured}`)\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-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"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 Q659 for service return patterns.","ek9Example":"#!ek9\ndefines 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 for :/api open\n\n      healthCheck() as GET for :/status\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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 Bytes: Bytes().withUtf8(`{\"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?","How do I avoid dispatcher handler ambiguity with multiple traits?","Why is Duck ambiguous when it implements two traits?","How do I resolve dispatcher ambiguity in EK9?"],"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":"#!ek9\ndefines 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","multiple","resolution","trait"],"primaryTopics":["dispatcher ambiguity","E05230","DISPATCHER_HANDLER_AMBIGUITY","multiple traits"],"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":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 - double quotes never interpolate, and ${ inside them is a compile error (E07901). 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":"#!ek9\ndefines 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_add_program","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":"#!ek9\ndefines 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_new_construct","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":"#!ek9\ndefines 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_new_construct","intent":"class","description":"Oracle can generate a class with constructor, methods, and operator declarations."}}
{"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- Simplest: give every field an initial value: 'xPos <- 0.0' not 'xPos as Float' (E08180). Alternatively declare it unset ('xPos as Float?'), provide a coded constructor that sets it AND make the no-arg constructor private ('default private Coordinate()'); without the constructor the compiler raises E07170, and with it but a public default constructor E07175\n- Add 'operator <=>' (comparator) with a matching 'operator #?' hashing the same fields; ==, <>, <, >, <=, >= are generated from '<=>', and 'default operator' auto-generates $ and ?\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":"#!ek9\ndefines 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        -> other as Coordinate\n        <- rtn as Integer: xPos <=> other.xPos\n\n      operator #? as pure\n        <- rtn as Integer: #?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_new_construct","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  missingName <- String()   // missingName? 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":"#!ek9\ndefines 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    ->\n      firstNum as Integer\n      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":"#!ek9\ndefines 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  other <- String()\n  name <- \"Steve\"        // DECLARE: creates new variable, type inferred\n  other :=: name          // COPY: copies the value of name into the existing object other\n  name := \"Different\"    // ASSIGN: changes existing variable\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 COPYING — copy the contents into an existing object, not the reference. Each type copies at its own level: a List copies its element references (the elements are shared), and the generated default :=: is shallow - each field then refers to the source's object; write your own :=: for a deeper copy (Q2100).\n\nThere are also conditional variants:\n  config <- String()\n  config :=? \"default\"   // assign only if config is currently UNSET\n\n  if value <- getValue()  // guard that DECLARES: the block runs only if the result is SET\n    stdout.println(value)\n\n  current <- String()\n  if current := getValue() then current?  // EXISTING variable: assign, then run the block only if SET\n    stdout.println(current)\n\nCommon mistake from Java/Python developers: using '<-' to reassign. A second '<-' on a name that already exists, in the same scope or a nested block, is compile error E50050 (Variable/Constant duplicated) - nothing is shadowed. Use ':=' to change an existing variable.\n\nSee Q22 for variable declaration. See Q29 for unset variables. See Q75 for guards.","ek9Example":"#!ek9\ndefines 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, ':=:' copy into an existing object (each type at its own level) — each has distinct semantics. Re-declaring with '<-' is an error (E50050), never shadowing.","keywords":["arrow","assign","colon","copy","declare","difference","operator","variable"],"primaryTopics":["<- operator",":= operator",":=: operator","assignment"],"typicalErrors":[{"error":"E50050","correct":"      counter := 10","incorrect":"      counter <- 10","explanation":"counter already exists, so a second '<-' is E50050 (Variable/Constant duplicated) - EK9 does not shadow. Use ':=' to change an existing variable. See ek9 -h E50050 for details."}],"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 declaration guards 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":"#!ek9\ndefines 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 and <- gives E50050 — why?","Can I shadow a variable with the <- operator in EK9?"],"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      // ERROR E50050: score already exists\n\nUsing '<-' on a name that already exists - in the same scope or in any nested block - is compile error E50050 (Variable/Constant duplicated). EK9 has no shadowing, so this mistake can never silently create a second variable: the compiler rejects it and you change the line to ':='.\n\nFor function returns, '<-' declares the return variable:\n  calculateTotal()\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":"#!ek9\ndefines 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 on one name is compile error E50050 (no shadowing).","keywords":["E50050","arrow","assign","declare","error","rebind","scope","shadow","variable"],"primaryTopics":["<- vs :=","variable shadowing","declaration"],"typicalErrors":[{"error":"E50050","correct":"      message := \"updated\"","incorrect":"      message <- \"updated\"","explanation":"message was already declared with '<-', so declaring it again is E50050 (Variable/Constant duplicated). EK9 never shadows a variable; use ':=' to change it. See ek9 -h E50050 for details."}],"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":"#!ek9\ndefines 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        -> other as Address\n        <- rtn as Integer: street <=> other.street\n\n      operator #? as pure\n        <- rtn as Integer: #?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 — no memory allocated to hold a value (what other languages call null); EK9 is designed so that a program can never observe it, and a missing Dict key gives the getOrDefault default, typically unset\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 Q1353 for a private super constructor.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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  defines program\n    MessageProgram()\n      <- message as String: \"done\"  // ERROR: E07590\n    SuccessProgram()\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\nEVERY CODE IS YOURS\nA program may return any Integer, including the numbers the ek9 command uses for its own failures (3, 8, 11 ...), so a script being replaced keeps the codes its callers expect. When EK9 itself fails - a compile error, or arguments that do not fit the program - its first stderr line says so: ek9: compilation failed (exit 8). Your program's exit never carries that line, so a caller tells the two apart from stderr, not from the number.\n\nOTHER WAYS A PROGRAM'S STATUS IS SET\nAn unset return exits 1. An Exception nothing catches exits with its own exit code (or 1). A Panic, such as a failed require, always exits 1. See Q1431.\n\nSee Q3 for printing output. See Q872 for program variables.","ek9Example":"#!ek9\ndefines 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  defines program\n    MyProgram()\n      -> argv as List of String\n      stdout <- Stdout()\n      stdout.println(`Arguments: ${argv}`)\n\nSTYLE 2: TYPED PARAMETERS (EK9 parses them from the command-line strings automatically)\n  defines program\n    MyProgram()\n      ->\n        name as String\n        count as Integer\n      stdout <- Stdout()\n      stdout.println(`${name} ${count}`)\n\nCANNOT MIX\n  defines program\n    MyProgram()\n      ->\n        count as Integer\n        argv as List of String    // ERROR: E07610\n      stdout <- Stdout()\n      stdout.println(`${count} ${argv}`)\n\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, Character, Bits, Date, Time, DateTime, Duration, Millisecond, Colour, Resolution, Dimension, Money and RegEx (plus the single List of String of style 1). Anything else - Path included - is E07600.\n\nSee Q3 for printing output. See Q888 for program return type.","ek9Example":"#!ek9\ndefines 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  defines service\n    StatusApi for :/api open\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":"#!ek9\ndefines module qa.webdeep.servicereturntype\n\n  defines service\n\n    <?-\n      API service with correct HTTPResponse return types.\n    -?>\n    StatusApi for :/api open\n\n      healthCheck() as GET for :/health\n        <- response as HTTPResponse: () with trait of HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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":"#!ek9\ndefines 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?","What is the maximum number of registrations in an EK9 application block?","Why does EK9 limit application registrations?","How do I split a large application block?"],"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\nTwelve is EK9's design limit, not a research figure: focused, modular composition keeps an application understandable, and a large registration block often means the application mixes unrelated concerns.\n\nSPLIT INTO FOCUSED APPLICATIONS\nApplications do not compose: there is no 'with application of' on an application, and a program links exactly ONE application. So split a large block into separate, focused applications, each selected by the program that needs it:\n  defines application\n    InventoryApp\n      register DbService() as AbstractDb\n      register StockService() as AbstractStock\n\n    OrderingApp\n      register DbService() as AbstractDb\n      register OrderService() as AbstractOrder\n\n  defines program\n    StockReport() with application of InventoryApp\n      ...\n\n    TakeOrders() with application of OrderingApp\n      ...\n\nEach application registers everything its own program injects - registrations are never inherited from another application. If one program genuinely needs more than twelve components, that is the signal the program itself is doing too much.\n\nSee Q667 for abstract injection. See Q798 for missing/duplicate registrations. See Q324 for DI basics.","ek9Example":"#!ek9\ndefines 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 focused applications, each linked from its own program.","keywords":["DI","E11042","application","limit","modular","monolith","quality","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 it into separate focused applications, each linked from its own program. 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":"#!ek9\ndefines 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":[],"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, or parameters inside the parentheses like Java (a syntax error with no error code)\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":"#!ek9\ndefines 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":[],"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. Mixing them up is an easy mistake to make, and the compiler reports it.\n\nTHREE OPERATORS\n<- is DECLARATION: creates a NEW variable with its first value.\n:= is ASSIGNMENT: assigns to an EXISTING variable.\n:=? is ASSIGN-IF-UNSET: 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\nASSIGN-IF-UNSET :=?\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":"#!ek9\ndefines 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 :=? assign-if-unset.\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      // :=? ASSIGN-IF-UNSET: 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 assign-if-unset.","keywords":["assignment","coalesce","create","declaration","declare","guard","operator","reassign","variable"],"primaryTopics":["declaration operator","assignment operator","assign-if-unset"],"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":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":"#!ek9\ndefines 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?","How do I check if a variable has a value in EK9?","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":"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  listIsSet <- emptyList?     // true - an empty list IS set\n\nSee Q24 for tri-state semantics.","ek9Example":"#!ek9\ndefines 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 a :=? chain as an alternative to a ternary expression.\n      EK9 does have a ternary (chosen <- isReady <- \"yes\" : \"no\"), but ? is the set-check, not a ternary.\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      // A :=? chain instead of a 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","condition","isSet","operator","question","set","set check","suffix","tri-state","unset","value check"],"primaryTopics":["? operator","isSet","tri-state","suffix operator","? set check","isSet in conditions"],"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":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 field visibility, and neither can be changed (there is no visibility modifier on a field). This catches many developers coming from Java where the field visibility is chosen by modifier.\n\nRECORD FIELDS: always PUBLIC\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: always PRIVATE\nLike a Java private field, 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":"#!ek9\ndefines 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: class fields with no modifier are package-private; record components are private final with public accessor methods. 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: record fields are always PUBLIC (like Kotlin data class), class fields are always PRIVATE (like a Java private field) — opposite fixed visibilities 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":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. It needs a returning variable that the body (and the catch) assign:\n  result <- try\n    <- rtn as String: \"default value\"\n    rtn: parseInput(rawText)\n  catch\n    -> ex as Exception\n    rtn: \"recovered\"\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\nException is the base type and is declared 'as open', so you can extend it with your own exception types.\nUse '$' on an exception to get its type and reason as a String ('Exception: <reason>'); ex.reason() gives just the reason text.\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":"#!ek9\ndefines 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(ex.reason())\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(ex.reason())\n      finally\n        stdout.println(\"Division complete\")\n\n      // Nested try/catch: the inner try sits inside the outer try body\n      try\n        firstResult <- safeDivide(20, 5)\n        stdout.println(`20 / 5 = ${firstResult}`)\n        try\n          secondResult <- safeDivide(8, 2)\n          stdout.println(`inner: ${secondResult}`)\n        catch\n          -> ex as Exception\n          stdout.println(ex.reason())\n      catch\n        -> ex as Exception\n        stdout.println(ex.reason())\n\n      // Try as an expression: the returning variable carries the value\n      parsed <- try\n        <- rtn as String: \"default value\"\n        rtn: `${safeDivide(9, 3)}`\n      catch\n        -> ex as Exception\n        rtn: `failed: ${ex.reason()}`\n      stdout.println(`parsed: ${parsed}`)","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":"      catch\n        -> ex as Exception\n        stdout.println(ex.reason())\n      finally","incorrect":"      catch\n        -> ex as String\n        stdout.println(ex)\n      finally","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 operators. A type can only define operators from that set - no new operator symbols can be created.\n\nTHE EK9 OPERATOR SET (the overloadable operators are the ones in OperatorMap)\nAssignment: <- (declaration), := : = (assignment), :=? (assign-if-unset)\nCopy/Replace/Merge: :=: (copy), :^: (replace), :~: (merge)\nComparison: == (eq), <> and != (neq), <=> (compare), <~> (fuzzy compare), < > <= >=\nCoalescing: ?: (left if SET), ?? (left unless ABSENT; an unset left passes through), <? >? <=? >=? (coalescing comparators)\nArithmetic: + - * / ^ (power) mod rem, ! (postfix factorial)\nShift: << >> (for example on Bits)\nMutation: ++ -- (postfix, statement-only), += -= *= /=\nConversion: $ (string), $$ (JSON), #? (hashcode), #^ (promote)\nFirst/last: #< (first) #> (last), e.g. #< \"hello\" is 'h'\nChecking: ? (isSet), ~ (negate/not)\nLogical: not and or xor\nCollection/String: contains matches\nUnary: length abs sqrt close empty\nStreams: | (pipe)\nTernary: condition <- valueIfTrue else valueIfFalse, or condition <- valueIfTrue : valueIfFalse\n\nWHAT DOES NOT EXIST\nC's ternary 'cond ? a : b', [] indexing, ?. safe navigation, and prefix ++/--.\n\nCORRECT ALTERNATIVES FOR PATTERNS FROM OTHER LANGUAGES\nConditional value: use EK9's ternary (grade <- score > threshold <- \"good\" else \"fail\"), a switch expression, or if/else assignment\nCollection access: use get() method on List/Dict\nSafe value access: use guard (if v <- expr())\nPrefix increment: use postfix (variable++ not ++variable)\n\nSee Q96 for operator definitions. See Q238 for operator enforcement rules.","ek9Example":"#!ek9\ndefines 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 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 - EK9 also has a ternary (cond <- a else b), just not C's cond ? a : b\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 — 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 EK9's own ternary (cond <- a else b, or cond <- a : b), not C's cond ? a : b, or a switch expression. Safe access uses declaration guards.","keywords":["AI","complete list","fixed set","indexing","mistake","operator","valid operators"],"primaryTopics":["invalid operators","operator hallucination","correct alternatives"],"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 change the object IN PLACE and return nothing (Void). Because they return nothing they are STATEMENT-ONLY: they cannot appear inside an expression.\n\nMUTATING OPERATORS\nThese operators modify the object in place:\n  - += -= *= /= — compound arithmetic\n  - ++ -- — increment/decrement\n  - :=: — copy another object's value into this one (each type at its own level; a List shares its elements)\n  - :^: — replace\n  - :~: — merge\n\nALIASING\nAliasing comes from '<-', not from the operator. Declaring a variable from another variable names the SAME object, so a mutating operator through either name is seen through both:\n  x <- 10\n  y <- x\n  x++\n  stdout.println(`${x} ${y}`)    // prints 11 11\n\nAnd 'y <- x++' does not compile at all: ++ is statement-only (E07950).\n\nCORRECT USAGE\n  counter <- 0\n  counter++\n  counter += 5\n\n  // For an independent copy, construct a NEW object\n  original <- 100\n  copied <- Integer(original)\n  copied++\n  stdout.println(`${original} ${copied}`)    // prints 100 101\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":"#!ek9\ndefines 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      // <- from a variable names the SAME object, so += is seen through both names\n      original <- 100\n      sharedName <- original\n      sharedName += 50\n      stdout.println(`Shared: ${original}, ${sharedName}`)\n\n      // An independent copy is a NEW object\n      source <- 100\n      copied <- Integer(source)\n      copied += 50\n      stdout.println(`Original: ${source}, 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 change the object in place and return nothing, so they are STATEMENT-ONLY (no 'y <- x++' puzzles); '<-' from a variable shares the object, so construct a new one (Integer(x)) when you need an independent copy.","keywords":["aliasing","compound","copy","decrement","expression","increment","mutating","operator","statement"],"primaryTopics":["mutating operators","aliasing","statement-only operators"],"typicalErrors":[{"error":"E07950","correct":"copied <- Integer(source)","incorrect":"copied <- source++","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 these generic variable names at compile time (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf, arg (and arg0, arg1, ...). Here is a replacement guide, banned names first.\n\nBANNED (E11031) - REPLACEMENT TABLE\n- temp, tmp -> temporaryUser, calculationResult\n- flag, flg -> isValid, hasPermission, wasProcessed\n- data, dat -> customerData, configData, requestPayload\n- object, obj -> user, order, session\n- value, val -> price, count, threshold\n- buffer, buf -> inputBuffer, responseBuffer\n- arg -> other, item, amount, message\n\nALLOWED, BUT OFTEN BETTER REPLACED (these compile; they are not banned)\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 '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":"#!ek9\ndefines 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":"#!ek9\ndefines 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. The inner $ is optional: ${age} interpolates an Integer directly, because interpolation calls the value's $ operator for you.\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":"#!ek9\ndefines 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":"E07901","correct":"ageText <- $age\n      stdout.println(ageText)","incorrect":"ageText <- \"${age}\"\n      stdout.println(ageText)","explanation":"A double-quoted String never interpolates, so '${' inside one is a compile error (E07901) - it would otherwise be the literal text '${age}'. Convert with the $ prefix operator ($age), or interpolate in a backtick String (`${age}`). See ek9 -h E07901 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, so ${ inside one is a compile error (E07901) - it is never quietly treated as text. Always use backtick strings for interpolation. To print the characters ${name} themselves, escape the dollar in a backtick string: `\\${name}` (any ${ inside a double-quoted string is E07901 - even one preceded by a backslash - so the literal text needs a backtick string).\n\nSee Q908 for $ as standalone operator. See Q242 for all conversion operators. See Q897 for operator overview.","ek9Example":"#!ek9\ndefines 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":"E07901","correct":"      stdout.println(`Age: ${age}`)","incorrect":"      stdout.println(\"Age: ${age}\")","explanation":"A double-quoted String never interpolates, so '${age}' inside one is a compile error (E07901); interpolation needs a backtick String. For the characters themselves write `\\${age}` in a backtick String. See ek9 -h E07901 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":"#!ek9\ndefines module qa.operators.dollarcustom\n\n  defines class\n\n    Hue\n      red as Integer: 0\n      green as Integer: 0\n      blue as Integer: 0\n\n      Hue() 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 Hue: Hue()\n\n      NamedColour()\n        ->\n          colourName as String\n          colour as Hue\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 Hue ===\n\n      red <- Hue(255, 0, 0)\n      stdout.println($red)\n\n      // === Custom $ on NamedColour (delegates to Hue.$) ===\n\n      namedRed <- NamedColour(\"Red\", red)\n      stdout.println($namedRed)\n\n      // === $ inside backtick interpolation ===\n\n      blue <- Hue(0, 0, 255)\n      stdout.println(`Blue is: ${blue}`)\n\n      // === Multiple custom types ===\n\n      green <- Hue(0, 255, 0)\n      namedGreen <- NamedColour(\"Green\", green)\n      stdout.println(`Hue: ${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\n        <- rtn as String: `rgb(${red}, ${green}, ${blue})`","incorrect":"operator $\n        <- rtn as String: `rgb(${red}, ${green}, ${blue})`","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_operator","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":"#!ek9\ndefines 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?","What does the #^ promote operator do in EK9?","How do I convert types in EK9?","What is #^ in EK9?","What does hash caret mean 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":"#!ek9\ndefines 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","introspection","operator","prefix operator","promote","type","type conversion","widening"],"primaryTopics":["#^ operator","_promote method","type widening","promote","type conversion"],"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  fromInteger <- $25           // returns \"25\" (Integer to String)\n  fromFloat <- $19.99          // returns \"19.99\" (Float to String)\n  fromBoolean <- $true         // returns \"true\" (Boolean to String)\n  fromString <- $\"hello\"       // returns \"hello\" (String to String, identity)\n  fromCharacter <- $'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":"#!ek9\ndefines 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. As an operator, # appears ONLY in four specific operator symbols: #?, #^, #< and #>. (It is also part of Money literals such as 10.50#GBP and Colour literals such as #FF0000, and of the #!ek9 first line.)\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 FOUR operators using #:\n- #? (hashcode) — returns Integer\n- #^ (promote) — returns a wider type\n- #< (prefix)\n- #> (suffix)\n\nThe last two work like this:\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 as an operator (not a comment; it only appears inside the four operators above, Money and Colour literals, and the first-line shebang)\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":"#!ek9\ndefines 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 (four # operators); # also appears in Money and Colour literals.","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":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":"#!ek9\ndefines 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  defines constant\n    EXCELLENT <- 90\n    POOR <- 40\n\n  defines function\n    classify() as pure\n      -> score as Integer\n      <- label as String: \"average\"\n      if score >= EXCELLENT\n        label: \"excellent\"\n      else if score < POOR\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":"#!ek9\ndefines 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}\"     // ERROR E07901 - double quotes never interpolate\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":"#!ek9\ndefines 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":"E07901","correct":"ageText <- $age","incorrect":"ageText <- \"Age: ${age}\"","explanation":"A double-quoted String never interpolates, so '${' inside one is a compile error (E07901) rather than literal text. Use $ as a prefix operator ($age), or interpolate in a backtick String (`Age: ${age}`). See ek9 -h E07901 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":"#!ek9\ndefines 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 these non-descriptive variable names at compile time via E11031: temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf, arg (and arg0, arg1, ...). 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, var, ret, res are NOT banned — only the 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":"#!ek9\ndefines 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 these non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf, arg (and arg0, arg1, ...). These are case-insensitive. Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, 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":"#!ek9\ndefines 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      // 'val' 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 these non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf, arg (and arg0, arg1, ...). These are case-insensitive. Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, 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":"#!ek9\ndefines 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 these non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf, arg (and arg0, arg1, ...). These are case-insensitive. Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, 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":"#!ek9\ndefines 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 these non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf, arg (and arg0, arg1, ...). These are case-insensitive. Names like input, output, param, 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":"#!ek9\ndefines 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 these non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf, arg (and arg0, arg1, ...). These are case-insensitive. Names like status, state, config, handle, str, num, item, result, input, output, count, index, list, map, set, key, type, param, 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":"#!ek9\ndefines 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 these non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf, arg (and arg0, arg1, ...). These are case-insensitive.\n\nNames like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, 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":"#!ek9\ndefines 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. AI mistakes from the banned list include: temp, data, value, buffer, flag, object.\n\nEK9 bans these non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf, arg (and arg0, arg1, ...). These are case-insensitive. Names like result, count, item, input, output, status, str, num, index, list, map, set, key, state, type, config, param, 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 banned names):\n- temp, tmp -> temporaryUser, calculationResult\n- flag, flg -> isValid, hasPermission, wasProcessed\n- data, dat -> customerData, configData, requestPayload\n- object, obj -> user, order, session\n- value, val -> price, count, threshold\n- buffer, buf -> inputBuffer, responseBuffer\n- arg -> other, item, amount, message\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":"#!ek9\ndefines 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: value <- calculateTotal(...)   BANNED\n      // FIXED: descriptive name\n      orderTotal <- calculateTotal(29.99, 3)\n      stdout.println(`Total: ${orderTotal}`)\n\n      // AI would write: tmp <- 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  upperWords <- 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 and the return requirement.","ek9Example":"#!ek9\ndefines 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 Q970 for output redirection. See Q237 for streams vs loops.","ek9Example":"#!ek9\ndefines 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 each element it is asked for, changing its type or value. The head stage stops the pull after N elements have passed through.\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 (doubler runs only for 5 and 1)\n\nHead is demand-driven: once N elements have passed, the stages before it are not asked for more, so the program here never calls doubler for 8 or 3. A stage that must see everything, such as sort, tail or join, always consumes all of its input, and a head after it only limits what it emits.\n\nSee Q235 for all operations. See Q125 for head/tail/skip details.","ek9Example":"#!ek9\ndefines module qa.streams.map.head.example\n\n  defines function\n\n    //Prints as it works, so the output shows how many elements map was asked for\n    doubler() as pure\n      -> number as Integer\n      <- rtn as Integer: number * 2\n      stdout <- Stdout()\n      stdout.println(`doubling ${number}`)\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      //head 2 stops the pull: doubler is never called for 8 or 3\n      cat [5, 1, 8, 3]\n        | map with doubler\n        | head 2\n        > stdout\n\n      //Transform every element, keep the first three results\n      labels <- cat [10, 20, 30, 40, 50]\n        | map with intToLabel\n        | head 3\n        | collect as List of String\n      stdout.println($labels)","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 Q970 for redirect. See Q954 for collect as custom type.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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?` asks: is this variable set?\n- `list?` asks: is this list set? (always true for created lists)\n- `result?` asks: is this result set?\n- `if connection?` is a 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":"#!ek9\ndefines 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      //Assign-if-unset 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' is CORRECT: a comparison produces Boolean\n- 'if name?' is CORRECT: ? produces Boolean\n- 'while isActive' is 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?' asks whether count is set and returns Boolean\n- 'if name?' asks whether name is set and returns Boolean\n\nUSE COMPARISONS FOR VALUE CHECKS\n- 'if count > 0' - a numeric comparison returns Boolean\n- 'if length name > 0' - checks that the String has content\n\nA TYPE OF YOUR OWN\nIf a type you write has a natural true/false reading, give it 'operator #^ as pure <- rtn as Boolean: ...'. Every condition - if, while, a ternary, require and assert - then accepts it, and the compiler inserts the promotion. It is explicit in the type's own definition and single-step (a type whose '#^' gives Integer is not a condition), so it is not truthiness.\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":"#!ek9\ndefines 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\nThe line directly under the switch line declares the return variable. The receiving variable is new with <- (result <- switch expr) or existing with := (result := switch expr). The return variable takes one of three forms:\n  - <- rtn as Type? : unset, nullable style\n  - <- rtn as Type: initialValue : with a default\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 and expression vs statement. See Q782 for return rules.","ek9Example":"#!ek9\ndefines 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  other <- 7 <? 20         // Result: 7\n\n>? RETURNS THE LARGER VALUE\n  maximum <- 10 >? 3       // Result: 10\n  larger <- 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 (coalesce, Elvis)\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":"#!ek9\ndefines 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  // C or Java: which updates first?\n  array[i++] = array[++j];\nEK9 eliminates this entire bug category by making ++ and -- void operations.\n\nIF YOU NEED THE OLD VALUE\n  saved <- Integer(counter)\n  counter++\n  // saved has old value, counter has new value\n\nTake a COPY with Integer(counter). A plain 'saved <- counter' names the SAME Integer object, and ++ changes that object in place, so both names would show the new value (see Q905).\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":"#!ek9\ndefines 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      //Copy before incrementing if you need the old value:\n      //'previous <- counter' would share the object and ++ would change both\n      previous <- Integer(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":"      previous <- Integer(counter)","incorrect":"      previous <- counter++","explanation":"The ++ operator is statement-only in EK9 — it returns void and cannot be used in expressions. Take a copy with Integer(counter) on its own line, then increment on the next. 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  logMessage()\n    -> msg as String\n    stdout <- Stdout()\n    stdout.println(msg)\n\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":"#!ek9\ndefines 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 E06270 when I pass the wrong function as an argument 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  defines function\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  defines function\n    doubleIt() is Transformer as pure\n      -> incoming as Integer\n      <- rtn as Integer: incoming * 2\n\nThen call it:\n  answer <- applyTransform(5, doubleIt)\n\nThe concrete function must match the abstract function's signature exactly. If the argument you pass does not match the parameter's function type, the compiler raises E06270 (parameter mismatch).\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":"#!ek9\ndefines 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?","Is an empty defines class section allowed 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\nIt is the SECTION that must not be empty. A single class with no members is legal; a defines section with nothing under it is E01087.\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      counter <- 0\n      default operator ?\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":"#!ek9\ndefines 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: an empty class (no members) is legal, but an empty defines SECTION is E01087 - every section must contain at least one declaration.","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 E07740 SERVICE_WITH_NO_BODY_PROVIDED?","What triggers E07670 SERVICE_OPERATOR_NOT_SUPPORTED?","Why is a protected helper method in a service rejected with E07240?","What operators can services use for CRUD?"],"answer":"EK9 services enforce three fundamental rules:\n\n1. CONCRETE BODIES REQUIRED (E07740)\nEvery service method must have a concrete implementation body. A method with no implementation is E07740 ('implementation not provided, but services cannot be abstract'): abstract methods cannot serve HTTP requests.\n\n2. SUPPORTED OPERATORS ONLY (E07670)\nA service may declare only these operators: +=, -=, :^:, :~: and ?. The CRUD ones are commonly mapped as:\n- += for POST (create)\n- -= for DELETE\n- :~: for PATCH (merge)\n- :^: for PUT (replace)\nAny other operator, such as == or <, is E07670.\n\n3. NO PROTECTED NON-WEB METHODS (E07240)\nA service may have helper methods that are not HTTP-bound, either plain or 'private'. What it may not do is mark such a method 'protected' (E07240): nothing can extend a service, so 'protected' has no meaning.\n\nSee Q657 for URI mapping. See Q659 for HTTPResponse. See Q660 for CRUD operators. See Q685 for method bodies.","ek9Example":"#!ek9\ndefines 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 (E07740).\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(\"  Service operators: +=, -=, :^:, :~: and ?\")\n      stdout.println(\"  Non-web helper methods cannot be protected\")\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 hold HTTP endpoints plus plain or private helper methods (never protected), with a fixed set of operators and every method given a body.","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_URI_WITH_VARS_NOT_SUPPORTED?","What triggers E07690 SERVICE_HTTP_ACCESS_NOT_SUPPORTED?","Why must a QUERY or HEADER service parameter be given a name (E07710)?","Why can a REQUEST service parameter not take a qualifier name (E07720)?","How do I bind path variables to parameters?"],"answer":"An EK9 service method becomes a web endpoint through its header, and its parameters say where each value comes from.\n\n1. VERB AND PATH\nA web method is written 'name() as VERB for :/path', with VERB one of GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS. The verb may be left out ('name() for :/path'), in which case the default verb is used. Path variables go in braces in the METHOD path: 'findUser() as GET for :/{userId}'.\n\n2. NO VARIABLES IN THE SERVICE BASE PATH (E07680)\nThe base path on the service name is fixed text. 'UserService for :/users/{tenant}' is E07680: a URI placeholder variable is not supported in that context.\n\n3. HTTP QUALIFIERS ONLY ON WEB METHODS (E07690)\nA parameter source marker such as ':=: QUERY \"q\"' only means something on a web method. On a method with no verb and no path - an ordinary helper - it is E07690 (HTTP access not supported in this context).\n\n4. PARAMETER QUALIFIERS AND THEIR NAMES (E07710, E07720)\nEach parameter can say where its value comes from: PATH, QUERY, HEADER, REQUEST, CONTENT or CONTEXT (there is no COOKIE qualifier). A path parameter needs no qualifier at all - it is bound from the matching '{name}' segment - and a bare PATH also compiles.\n  -> term as String :=: QUERY \"q\"\nQUERY and HEADER must name the query parameter or header: a bare QUERY or HEADER is E07710. REQUEST, CONTENT and CONTEXT take no name: 'REQUEST \"r\"' is E07720.\n\nSee Q657 for URI mapping. See Q846 for parameter types. See Q868 for path parameter types.","ek9Example":"#!ek9\ndefines 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, @RequestHeader. Python Flask: @app.route with <param>. Go: mux.Vars(r) for path variables. EK9: 'as VERB for :/path/{var}' on the method (the verb is optional), path variables bound by name, and ':=: QUERY \"q\"' / ':=: HEADER \"h\"' qualifiers for other sources.","keywords":["E07680","E07690","E07710","E07720","HEADER","HTTP","QUERY","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_HTTP_PATH_PARAM_COUNT_INVALID?","What does E07770 report when a REQUEST binding is not typed HTTPRequest?","What does E07780 report when HTTPRequest sits beside other parameters?","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    ->\n      catId as Integer\n      itemId as Integer\n    <- response as HTTPResponse: ...\nTwo path variables, two parameters — correct.\n\n2. HTTPREQUEST RULES (E07770, E07780, E07790)\nA parameter bound with :=: REQUEST must be typed HTTPRequest (E07770), and HTTPRequest used as a path/query/header parameter instead is E07790. An HTTPRequest parameter must be the ONLY parameter of the method (E07780).\n\nSee Q657 for URI mapping. See Q846 for parameter type rules. See Q868 for path parameter types.","ek9Example":"#!ek9\ndefines 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","E07790","HTTPRequest","count","match","parameter","path","service","variable"],"primaryTopics":["service path matching","E07730","E07770","E07780","E07790"],"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 the built-in types listed below (String, Integer, Float, Boolean, Character and more). 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, Character, Bits, Date, Time, DateTime, Duration, Millisecond, Colour, Resolution, Dimension, Money and RegEx.\n\nUNSUPPORTED (E07600)\nCustom classes, records, traits, enumerations, Dict, Optional, Path, GUID, or any user-defined type. (A List is only accepted as the single List of String parameter of style 1.)\n\nWORKAROUND\nUse String parameters and parse manually:\n  defines program\n    MyProgram()\n      -> configPath as String\n      stdout <- Stdout()\n      stdout.println(`Parse ${configPath} into your custom type`)\n\nSee Q889 for program argument styles. See Q888 for program return type.","ek9Example":"#!ek9\ndefines 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 E06010 missing type parameters?","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 E06010.\n\nCORRECT USAGE\n  names <- List() of String\n  mapping <- Dict() of (String, Integer)\n  maybe <- Optional() of Float\n\nINCORRECT USAGE (E06010)\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":"#!ek9\ndefines 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: E06010 enforces parameterization, no raw types ever.","keywords":["Dict","E06010","List","generic","parameter","parameterization","raw","required","type"],"primaryTopics":["generic parameterization required","E06010","missing type parameters"],"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 MISSING_OPERATOR_IN_SUPER?","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: in an extending class the SUPER must have the operator, and property types must support the required operators.\n\n1. THE SUPER MUST HAVE THE OPERATOR (E07190)\nA defaulted operator in an extending class calls the super's operator first, then the new fields. So a subclass may use 'default operator X' only if its super has operator X. If the super lacks it, compilation fails with E07190: add X to the super (written or defaulted), or write X by hand in the subclass. A parent and a subclass may both say 'default operator $'. The one case where the parent having it blocks the child is sameness (<=> or ==, and #?), which is declared once per hierarchy (E07182) - see rule 3. See Q1314.\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 the operator, compilation fails with E07200.\n\n3. SAMENESS IS REQUESTED ONCE\nRequest the sameness only: 'default operator <=>' (or 'default operator ==' for a type with a field that cannot be ordered, such as a List). ==, <>, <, <=, >, >= are then generated from it; also asking for 'default operator ==' or 'default operator <>' beside '<=>' is E07181.\n\nDEFAULT OPERATOR BEHAVIOR\n'default operator' generates field-by-field implementations:\n  default operator <=> compares all fields in declaration order (== and <> follow from it)\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":"#!ek9\ndefines 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; == and <> are generated from <=>\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: \"x\"    // 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":"#!ek9\ndefines 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 error do I get when I pass a class where a function is expected?","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 is a parameter mismatch at the call site (E06270).\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())    // E06270 parameter mismatch: MyClass is not an Assessor\n\nSee Q254 for Any type. See Q258 for type coercion. See Q255 for method resolution.","ek9Example":"#!ek9\ndefines 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":["E06270","class","construct","function","genus","kind","mismatch","parameter","record","trait"],"primaryTopics":["parameter genus","E06270","parameter 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":"#!ek9\ndefines 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\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 the time limit (the code is declared; no timeout setting is documented here)\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":"#!ek9\ndefines 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 (execution exceeded the time limit)\")\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 has no no-argument constructor, or its no-argument constructor is private (so 'Counter()' could not be written where the pipeline is), the compiler reports E07835, naming the type.\n\nSee Q235 for all stream operations. See Q931 for tail and skip. See Q122 for collect into built-in types.","ek9Example":"#!ek9\ndefines 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":"result <- cat [1, 2, 3, 4, 5] | collect as Counter","incorrect":"result <- cat [1, 2, 3, 4, 5] | 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?","Can I give a package a name property?"],"answer":"A package block holds only the properties a package defines, each with one shape. 'version' is a VersionNumber literal, 'description' and 'license' String literals, 'publicAccess', 'applyStandardIncludes' and 'applyStandardExcludes' Boolean literals; 'tags', 'includeFiles', 'excludeFiles' and 'capabilities' are a List of String literals, and 'deps', 'devDeps' and 'excludeDeps' a Dict of (String, String) literals. The wrong kind of literal (version <- \"1.0.0\", publicAccess <- \"yes\"), an expression instead of a literal (version <- 1 + 2), the wrong shape (tags <- \"tools\") or a property name the package does not define triggers E06260. There is no 'name' property: the module name IS the package name. If a package is declared, 'description' is mandatory (E50001 when it is missing).\n\nPACKAGE PROPERTY SHAPES\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  tags <- [\"tools\"]              // List of String literals\n  deps <- {\"a.b\": \"1.0.0-0\"}     // Dict of (String, String) literals\n\nWHY ENFORCED\nPackage metadata is used by the build system and dependency management. Invalid shapes would break version resolution, licensing, and publication. The compiler, the language server and every tool check each property the same way, before compiling.\n\nSee Q10 for module structure. See Q852 for reserved module names. See Q14 for package organization.","ek9Example":"#!ek9\ndefines module qa.classes.paramtypemismatch\n\n  defines package\n\n    version <- 1.0.0-0\n    description <- \"Demonstrates correct package property types\"\n    license <- \"MIT\"\n    tags <- [\"examples\"]\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":[{"error":"E06260","correct":"    version <- 1.0.0-0","incorrect":"    version <- \"1.0.0-0\"","explanation":"A package version is a VersionNumber literal (major.minor.patch-build), not a String; quoting it makes it a String literal of the wrong kind. See ek9 -h E06260 for details."},{"error":"E06260","correct":"    description <- \"Demonstrates correct package property types\"","incorrect":"    description <- 12","explanation":"A package description must be a String literal; an Integer (or any other kind of literal) is rejected. See ek9 -h E06260 for details."},{"error":"E06260","correct":"    tags <- [\"examples\"]","incorrect":"    tags <- \"examples\"","explanation":"'tags' is a List of String literals, even for a single tag - a lone String is the wrong shape. The same holds for includeFiles, excludeFiles and capabilities. See ek9 -h E06260 for details."},{"error":"E06260","correct":"    license <- \"MIT\"","incorrect":"    license <- \"MIT\"\n    name <- \"qa.classes.paramtypemismatch\"","explanation":"There is no 'name' package property - the module name IS the package name - so 'name' is unusable, as is any name the package does not define. See ek9 -h E06260 for details."}],"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":"#!ek9\ndefines 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 function\n\n    //Opaque, so the constraint is checked at RUNTIME.\n    ageFromRuntimeInput()\n      <- rtn as Integer: 200\n\n  defines program\n\n    TypeConstraintDemo()\n      stdout <- Stdout()\n\n      if validAge <- ValidAge(25)\n        stdout.println(`Valid age: ${validAge}`)\n\n      if name <- ShortName(\"Alice\")\n        stdout.println(`Valid name: ${name}`)\n\n      //This value is NOT statically known, so the constraint is checked at runtime and a breach\n      //Panics here. Had it been knowable — a literal, a constant, or a variable carrying one —\n      //it would instead be the compile error E08260, before the program ever ran.\n      badAge <- ValidAge(ageFromRuntimeInput())\n      stdout.println(`Bad age set: ${badAge?}`)","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\n        -> message as String\n\nNOT INJECTABLE (E08160)\ndefines class — user-managed objects\ndefines record — data-only aggregates\ndefines trait — interface contracts\ndefines function — callable units\n\nCORRECT PATTERN\n  notifier as NotificationService!    // OK: NotificationService is abstract component\n\nINCORRECT PATTERN\n  defines class\n    HelperClass\n      value as String: String()\n      default operator ?\n  defines program\n    Demo()\n      helper as HelperClass!  // ERROR 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":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?","What is a data clump and when does EK9 detect it?","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 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 as Float: Float()\n      longitude as Float: Float()\n      altitude as Float: Float()\n      heading as Float: Float()\n      default operator ?\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\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\nSee Q313 for code smells. See Q310 for quality overview.","ek9Example":"#!ek9\ndefines 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","Fowler","boundary","clean-code","clump","data","extract","function","limit","parameter","quality","record","refactoring","smell","threshold"],"primaryTopics":["data clump","E11053","DATA_CLUMP_DETECTED","parameter clump"],"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 Q243 for the full coalescing operator family. See Q24 for tri-state semantics.","ek9Example":"#!ek9\ndefines 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 Q243 for the full coalescing operator family. See Q963 for <? coalescing minimum.","ek9Example":"#!ek9\ndefines 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(\"Unexpectedly set: the result has a value\")\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  years as Integer := 30   same effect\n  months as Integer = 30   same effect\n\n:=? ASSIGN-IF-UNSET: 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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?\nThe mutating operators (++, --, +=, -=) return no value - they change the existing object in place and are void. So there is nothing for an expression to use, and writing one is E07950. Because they mutate in place, remember that a plain y <- x shares ONE object between y and x: a later x++ or x += 5 changes what y shows too. When you need an independent value, take a copy: y <- Integer(x).\n\nBridge: Like C++/Java's ++ but restricted to statements. Similar to how Python chose not to have ++ at all — EK9 allows it, but only as a statement that returns no value.\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":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?","How do I output stream results to stdout in EK9?","How do EK9 stream pipelines output data?","What is the > operator in EK9 streams?","How do streams print to console 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 (items are appended to what it holds)\n  >> myList         does the same: it also appends\n  collect as Type   materializes stream into new collection\n\nIMPORTANT: Use > like Unix redirect — NOT stdout <- cat items.\n\nCORRECT:\n  cat items\n    | filter by isValid\n    > stdout\n  cat numbers\n    | sort by ascending\n    | head 5\n    > stdout\n  upper <- cat names\n    | map with toUpperCase\n    | 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":"#!ek9\ndefines 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":["Unix","cat","collect","filter","map","output","pipe","pipeline","redirect","sink","stdout","stream","stream output","terminal"],"primaryTopics":["stream terminals","> operator in streams","collect as","stream output","redirect operator","Unix pipes"],"typicalErrors":[],"companions":[]}
{"id":971,"category":"Control Flow","question":"How do guards work in EK9 if statements?","url":"https://ek9.io/qa/QA0971.html","alternatePhrasings":["What is a guard 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":"Guards combine variable handling with isSet checking in a single statement. The block only executes if the guarded variable is SET.\n\nTHE GUARD FORMS:\n\n1. DECLARATION GUARD (<-):\n  if name <- getName()\n    stdout.println(name)\nDeclares name; the block runs only if it is SET.\n\n2. RE-POINT AND CHECK (:= then x?):\n  selectedItem <- String()\n  if selectedItem := findItem(\"key\") then selectedItem?\n    stdout.println(selectedItem)\nAlways assigns the result to the existing variable (an unset result included); the explicit 'selectedItem?' then runs the block only if it is SET.\n\n3. ASSIGN IF UNSET (:=?):\n  config <- loadDefaults()\n  if config :=? loadOverrides()\n    stdout.println(config)\n':=?' tests the LEFT side: it assigns (and only then calls loadOverrides()) when config is currently UNSET. Here config is already set by loadDefaults(), so it is kept, loadOverrides() is not called, and the block runs with the defaults.\n\nPLAIN ASSIGNMENT (:=) CHECKS NOTHING\n':=' in a control-flow head is a blind assignment: no isSet check. So 'if x := expr()' on its own has no condition and is rejected (E07340); add one with 'with' or 'then', or use a guard. In 'switch v := expr()' the switch runs whatever the value is, and an unset value goes to default.\n\nSCOPE AND ORDER\nA '<-' guard DECLARES a new variable that belongs to the if/else chain: the else branches can read it, and after the chain it is gone (naming it there is E50001). ':=' and ':=?' assign an EXISTING variable, so the value is still there after the chain - and ':=' on a name nothing declared is E50001, because ':=' never declares. Each pre-flow runs once, when its block is REACHED: in 'else if v <- next()' next() is called only after the earlier conditions were false, never when an earlier branch ran.\n\nWITH ADDITIONAL CONDITION:\n  if name <- getName() then name.length() > minLength\n    stdout.println(name)\nRuns only if name is SET AND the condition holds.\n\nThe same guard forms work in every control-flow statement:\n  if v <- expr()                     declaration guard\n  switch v <- expr()                 declaration guard in switch\n  while v <- expr() with condition   the guard runs ONCE, before the loop\n  for v <- expr() then i in items    the guard runs ONCE, before the loop\n  try v <- expr()                    an unset guard skips the whole try\nwhile and for always need their own condition or loop after 'with'/'then': the guard is an entry gate that runs once, and only that condition is re-checked on each iteration.\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. See Q77 for while guards. See Q1023 for the guard operators side by side.","ek9Example":"#!ek9\ndefines 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      // ASSIGN IF UNSET (:=?): the target is already set, so it is kept\n      chosen <- findGreeting(\"Dana\")\n      if chosen :=? findGreeting(\"Eve\")\n        stdout.println(`Kept: ${chosen}`)\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 work in if, switch, while, for and try; in while and for the guard runs once, before the loop; ':=?' assigns only when the target is unset.","keywords":["control flow","declaration guard","guard","isSet check","null safety"],"primaryTopics":["guards","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 declaration guards 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":"#!ek9\ndefines 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 DECLARATION GUARD. 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":"#!ek9\ndefines 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":["declaration guard","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":"#!ek9\ndefines 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 guards 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 declaration guard work in switch statements?","Show me guards in while loops in EK9","Is the if v <- expr guard universal across EK9 control flow?","Can I use guards in switch statements and while loops, not just if?","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. The same 'v <- expression' guard works in if, switch, while and try: it means 'declare v, check if SET, only proceed if SET'. One difference to know: in a loop the guard is evaluated ONCE, on entry (if it is unset the body is skipped); only the 'with' condition is re-tested on every iteration.\n\nIN IF:\n  if name <- findUser(id)\n    stdout.println(name)\n\nIN SWITCH:\n  switch kind <- database.kindOf(id) with kind\n    case == \"USER\"\n      processUser(kind)\n    default\n      processOther(kind)\n\nIN WHILE (the guard runs once; 'with' is the per-iteration test):\n  while conn <- openConnection() with counter < maxIters\n    process(conn)\n    counter: counter + 1\n\nIN TRY:\n  try connection <- openDatabase()\n    query(connection)\n  catch\n    -> ex as Exception\n    logError(ex)\n\nThe guard syntax is the same everywhere - one syntax to learn, and it works the same across ALL control flow.\n\nSee Q971 for if guard details. See Q75 for switch guard details.","ek9Example":"#!ek9\ndefines 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(missing)\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: the same guard syntax works in if, switch, while, and try (in a loop the guard is evaluated once, 'with' is re-tested).","keywords":["control flow","guard","switch","switch guard","try","try guard","universal","while","while guard"],"primaryTopics":["universal guard pattern","guards in all control flow","guards in switch","guards in while","universal guards"],"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":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":"#!ek9\ndefines 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":"#!ek9\ndefines 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. With an ascending comparator those are the N smallest; use a descending comparator for the N largest:\n\n  cat prices\n    | sort by ascending\n    | head 3\n    > stdout\n\nhead N replaces the break-after-N pattern from other languages: once N items have passed it, nothing more is pulled. A sort has to see every item before it can emit the smallest, so here the sort still reads all of prices and head stops only its emission after N items; with no sort before it, head stops reading the source itself.\n\nUse > stdout to print results, or collect as to gather into a collection.","ek9Example":"#!ek9\ndefines 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(\"Cheapest 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\n    | reject by isBlank\n    | tee in backup\n    | head 5\n    > 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.\n\nA tee is a lazy peek: it records only the items that flow past it. In the first example head 5 stops the pull, so backup holds the same (at most) 5 non-blank items that are printed, not every non-blank item in the list. Put the tee before a stage that must see everything (sort, tail, join), or drop the head, to capture them all.","ek9Example":"#!ek9\ndefines 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":"E50001","correct":"| tee in validReadings","incorrect":"| tee in savedReadings","explanation":"tee copies items into a collection that must already exist. 'savedReadings' is not declared anywhere, so E50001 is reported on that line. Declare it first (validReadings <- List() of Float) and name it in the tee. See ek9 -h E50001 for details."}],"companions":[]}
{"id":982,"category":"Operators and Expressions","question":"How do ? and guards 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    process(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":"#!ek9\ndefines 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 — calling get(), ok(), error() or next() on an Optional, Result or Iterator without first checking it. Use a guard 'if v <- expr()' or check with v? before the access.\n\nUse 'ek9 -h Exxxxx' for detailed explanation of any error code.","ek9Example":"#!ek9\ndefines 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. Not ?? - it only replaces an ABSENT value, so an unset one passes straight through (see Q1059).","ek9Example":"#!ek9\ndefines 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 ?: (Elvis) depending on the scenario.","keywords":["assign","choose","conditional","if else","switch expression","value"],"primaryTopics":["conditional value assignment","choosing between values"],"typicalErrors":[],"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?","Does ?? or ?: give an unset value a default in EK9?","How do I use :=? for conditional initialisation?"],"answer":"EK9 provides two operators for default values:\n\n?: COALESCE (ELVIS)\nReturns the left value if SET, otherwise the right:\n  displayName <- userName ?: \"Guest\"\n  timeout <- configuredTimeout ?: defaultTimeout\n\n:=? ASSIGN-IF-UNSET\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)\n\nNOT ?? FOR DEFAULTS\n?? (null coalescing) only tests whether the left operand is ABSENT. An unset value such as String() is present, so ?? passes it straight through and the result is still unset:\n  name <- String()\n  name ?? \"Guest\"       still unset\n  name ?: \"Guest\"       \"Guest\"\nSee Q1059 for the difference between ?? and ?:.","ek9Example":"#!ek9\ndefines 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 (?? would pass the unset value through)\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 ?: (elvis) for an inline default and :=? for assign-if-unset. Both handle unset values without any concept of null. Unlike C#/Swift/JavaScript, EK9's ?? does NOT default an unset value - it only replaces an absent one.","keywords":["coalescing","default","fallback","guarded","unset"],"primaryTopics":["default values","?: coalesce (Elvis)",":=? assign-if-unset"],"typicalErrors":[],"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 <- declaration guard, 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":"#!ek9\ndefines 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 <- guards. 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":"E50001","correct":"      if wordCount <- computeLength(words) then wordCount > minimumSize\n        stdout.println(`Has ${wordCount} words - enough to process`)","incorrect":"      wordCount := computeLength(words)\n      if wordCount > minimumSize\n        stdout.println(`Has ${wordCount} words - enough to process`)","explanation":"EK9's := only updates a variable that already exists; unlike Python's walrus it never creates one, and it is a statement, not an expression, so it cannot sit inside a condition. Writing 'wordCount := ...' for a new name is E50001 - not resolved. Declare with <- instead, using the guard pattern 'if v <- expr() then condition'. See ek9 -h E50001 for details."}],"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 in five steps:\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 sorted items, then stops the emission\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, nothing more is pulled. A head stops the pull back to the nearest stage that must see everything: here that is the sort, which still has to read every large order to know which are biggest, so the filter runs on every amount; only the sorted items beyond the first 3 are never emitted. Without the sort, head 3 would stop the filter after the third large order.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"E50060","correct":"      highScores <- cat allScores\n        | filter by isAboveCutoff\n        | collect as List of Integer","incorrect":"      highScores <- allScores.stream()","explanation":"A List has no stream() method, so the call is not resolved (E50060). EK9 streams use pipe syntax, not Java method chaining. Write 'cat source | filter by fn | collect as Type'. See ek9 -h E50060 for details."}],"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\n    | sort by keyComparator\n    | group by keyExtractor\n    | flatten\n    > 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":"#!ek9\ndefines 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 hashes into a Map and so needs no sorted input.","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 a List holding only its minimum-priced item\n4. Flatten those one-item Lists back into Products, then collect or output them\n\nSTREAM PIPELINE:\n  cheapest <- cat products\n    | sort by compareCategory\n    | group by extractCategory\n    | map with findCheapestInGroup\n    | flatten\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 a List of Product holding only the one with the lowest price\n\nThe findCheapestInGroup function itself can use a stream. A stream collects into a List, so the function returns a List and the pipeline flattens it afterwards:\n  findCheapestInGroup() as pure\n    -> group as List of Product\n    <- rtn as List of Product: cat group\n      | sort by comparePrice\n      | head 1\n      | collect as List of Product\n\nThis decomposes a complex task into small, testable, pure functions connected by a pipeline.","ek9Example":"#!ek9\ndefines 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    findCheapestInGroup() as pure\n      -> group as List of Product\n      <- rtn as List of Product: cat group\n        | sort by comparePrice\n        | head 1\n        | collect as List of Product\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, find the cheapest in each group, flatten the one-item lists\n      stdout.println(\"Cheapest product in each category:\")\n      cat products\n        | sort by compareCategory\n        | group by extractCategory\n        | map with findCheapestInGroup\n        | flatten\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 guards. 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 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":"#!ek9\ndefines 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 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- The accessors and describe() are declared 'as pure', so they are known to have no side effects\n\nIMPROVEMENT OPPORTUNITIES:\n- The deposit and withdraw methods could validate inputs (e.g., negative amounts)\n- To prevent a negative balance, withdraw could apply the amount only when withdrawalAmount <= currentBalance. Do not reach for ':=?' here: it assigns only when the field is UNSET, so on a set balance the withdrawal would silently do nothing\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":"#!ek9\ndefines 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":"      account.withdraw(200.0)\n      stdout.println(account.describe())","incorrect":"      account.withdraw(200.0)\n      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 my discount lookup. 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 called an accessor on an Optional, Result or Iterator (get(), ok(), error(), next()) without first proving it holds a value. It is only raised for those types: an ordinary value such as a Float that might be unset can be used directly, and the result is simply unset too.\n\nIN THIS CODE:\nlookupDiscount returns an Optional of Float - empty for an unknown product. The failing code calls .get() straight away:\n  lookupDiscount()\n    -> productCode as String\n    <- rtn as Optional of Float: Optional() of Float\n    if productCode == \"PREMIUM\"\n      rtn: Optional(0.20)\n\n  basePrice <- 100.0\n  discount <- lookupDiscount(\"PREMIUM\")\n  finalPrice <- basePrice - (basePrice * discount.get())   // ERROR E08030: not checked before access\n  stdout.println($finalPrice)\n\nTHE FIX:\nUse a declaration guard, which declares the variable AND checks that it is set:\n  if discount <- lookupDiscount(productCode)\n    finalPrice <- basePrice - (basePrice * discount.get())\n\nThe body only runs when the Optional holds a discount, so the get() inside it is safe. 'if discount?' after a separate declaration works the same way.\n\nALTERNATIVELY:\nAsk the Optional for a default, so there is nothing to check:\n  rate <- lookupDiscount(productCode).getOrDefault(0.0)\n  finalPrice <- basePrice - (basePrice * rate)\n\nThis uses 0.0 (no discount) when the Optional is empty.","ek9Example":"#!ek9\ndefines module qa.debugging.diagnoseerror\n\n  defines function\n\n    lookupDiscount() as pure\n      -> productCode as String\n      <- rtn as Optional of Float: Optional() of Float\n\n      premiumCode <- \"PREMIUM\"\n      standardCode <- \"STANDARD\"\n\n      if productCode == premiumCode\n        rtn: Optional(0.20)\n      else if productCode == standardCode\n        rtn: Optional(0.10)\n\n  defines program\n\n    DiagnoseErrorDemo()\n      stdout <- Stdout()\n\n      basePrice <- 100.0\n\n      //Correct: the guard checks the Optional, so get() inside the body is safe\n      if discount <- lookupDiscount(\"PREMIUM\")\n        finalPrice <- basePrice - (basePrice * discount.get())\n        stdout.println(`Discounted price: ${finalPrice}`)\n\n      //Correct: getOrDefault needs no check - an unknown product gets 0.0\n      standardDiscount <- lookupDiscount(\"UNKNOWN\").getOrDefault(0.0)\n      regularPrice <- basePrice - (basePrice * standardDiscount)\n      stdout.println(`Regular price: ${regularPrice}`)","migrationContext":"E08030 in EK9 is like calling Optional.get() in Java without isPresent(), or unwrap() in Rust - except EK9 rejects it at compile time instead of throwing at runtime. There is no escape hatch.","keywords":["E08030","Optional","access","diagnose","fix","getOrDefault","guard","unsafe"],"primaryTopics":["E08030 diagnosis","unchecked Optional access"],"typicalErrors":[{"error":"E08030","correct":"      if discount <- lookupDiscount(\"PREMIUM\")\n        finalPrice <- basePrice - (basePrice * discount.get())\n        stdout.println(`Discounted price: ${finalPrice}`)","incorrect":"      discount <- lookupDiscount(\"PREMIUM\")\n      finalPrice <- basePrice - (basePrice * discount.get())\n      stdout.println(`Discounted price: ${finalPrice}`)","explanation":"Calling get() on an Optional that has not been checked is rejected. Use a guard 'if v <- expr()' (or 'if v?') before get(), or getOrDefault(). See ek9 -h E08030."},{"error":"E01073","correct":"      if discount <- lookupDiscount(\"PREMIUM\")\n        finalPrice <- basePrice - (basePrice * discount.get())\n        stdout.println(`Discounted price: ${finalPrice}`)","incorrect":"      discount <- lookupDiscount(\"PREMIUM\")\n      if discount != null\n        finalPrice <- basePrice - (basePrice * discount.get())\n        stdout.println(`Discounted price: ${finalPrice}`)","explanation":"'null' does not exist in EK9. Use a guard 'if v <- expr()' to check the Optional, or the ? suffix: 'if discount?'. 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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_operator","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":"#!ek9\ndefines 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":"E07950","correct":"      evenCount++","incorrect":"      otherCount <- evenCount++","explanation":"++ is a standalone statement in EK9 that does not return a value, so it cannot be used on the right of an assignment such as 'otherCount <- evenCount++'. Write 'evenCount++' on its own line. See ek9 -h E07950 for details."}],"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","How does a function return a value in EK9? There is no return keyword.","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":"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    knownUser <- 42\n    if userId == knownUser\n      rtn: \"Alice\"\n    //rtn stays unset if no match found","ek9Example":"#!ek9\ndefines 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","no return keyword","return","rtn","value"],"primaryTopics":["function return pattern","<- rtn declaration","function return mechanism","no return keyword"],"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_new_construct","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  defines function\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":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","Print all items from a list directly to stdout using a pipeline.","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":"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":"#!ek9\ndefines 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","output","pipeline","print","stdout","stream","terminal"],"primaryTopics":["first stream pipeline","cat and > stdout","cat to stdout","stream output"],"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\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 (negative, zero or positive)\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 generates _string, _json, _hashcode, _isSet, _cmp (with _eq, _neq and the ordering comparisons derived from it) and _copy from your declared fields. It does NOT generate #^ (_promote), :~: (_merge) or :^: (_replace) - those must be written by hand, and using them on a type without them is a compile error. Implement any operator manually for custom behaviour.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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 assign-if-unset (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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"      -> number as Integer\n      <- rtn as Integer: number * 2","incorrect":"      -> number as Integer\n      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 the same ?: elvis operator for defaults (EK9's ?? only replaces an absent value, not an unset one). 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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. GUARDS (most common)\nGo:  result, err := doSomething(); if err != nil { handleError(err) }\nEK9:\n  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:\n  divide() as pure\n    ->\n      a as Float\n      b as Float\n    <- rtn as Result of (Float, String): Result() of (Float, String)\n    if b?\n      rtn: rtn.asOk(a / b)\n    else\n      rtn: rtn.asError(\"no divisor\")\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":"#!ek9\ndefines 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, d); if err == nil { use(result) } else { handleError() }\n      // The first denominator succeeds, the second returns unset so the else branch runs\n      for denominator in [3.0, 0.0]\n        if answer <- safeDivide(10.0, denominator)\n          stdout.println(`Result: ${answer}`)\n        else\n          stdout.println(\"Division returned unset (like Go's err != nil)\")\n\n      // With ?: default (like Go's 'or default' pattern) - ?? would pass the unset Float through\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":"#!ek9\ndefines 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 getOrDefault(key, default) or check with contains, not [] indexing.","keywords":["collection","dict","dictionary","key value","migration","python"],"primaryTopics":["Python dict to EK9 Dict","dictionary migration"],"typicalErrors":[{"error":"E50060","correct":"      aliceScore <- scores.getOrDefault(\"Alice\", 0)","incorrect":"      aliceScore <- scores.get(\"Alice\")","explanation":"Dict has no get() method and EK9 has no [] indexing operator. Use getOrDefault(key, default), or check with contains first, to access Dict values. See ek9 -h E50060 for details."}],"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\nThe resource type must declare `operator close as pure` (a method simply named close() is an error, E07660, because close is an operator name). EK9 calls that operator automatically when the try statement ends - like Python's __exit__() or Java's AutoCloseable. The resource class also needs `override operator ? as pure`.","ek9Example":"#!ek9\ndefines module qa.gettingstarted.frompythonconnection\n\n  defines class\n\n    Connection\n      queryName()\n        <- rtn as String: \"Database User\"\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n      // Called automatically when the try statement ends\n      operator close as pure\n        Stdout().println(\"Connection closed\")\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 <- Resource() ... catch/finally\n      // The resource's 'operator close as pure' runs when the try statement ends\n\n      try\n        -> connection <- Connection()\n        greeting <- buildGreeting(connection.queryName())\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 resource's `operator close as pure` replaces __exit__ (a finally block is separate and runs its own code). 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        -> connection <- Connection()\n        greeting <- buildGreeting(connection.queryName())\n        stdout.println(greeting)\n      catch\n        -> ex as Exception\n        stderr.println(`Error: ${ex}`)\n      finally\n        stdout.println(\"Cleanup complete\")","incorrect":"      connection <- Connection()\n      if connection <> null\n        greeting <- buildGreeting(connection.queryName())\n        stdout.println(greeting)","explanation":"'null' does not exist in EK9, so a Python-style 'is not None' check has no equivalent. Use try-with-resource for connections: 'try -> connection <- expr()', where the resource type declares 'operator close as pure'. 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\nTest the Boolean itself ('if entry.active'), not 'entry.active?': '?' asks whether the value is SET, and false is a set value, so 'if entry.active?' would list inactive records too.\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":"#!ek9\ndefines 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?","Why does \"Hello ${name}\" not work like a Python f-string 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- ${expression} calls the expression's $ operator, so any type that has one (Integer, Float, Boolean ...) interpolates directly: ${count} needs no extra conversion\n- Double-quoted strings \"...\" are plain text — no interpolation, so a ${ inside one is a compile error (E07901)\n- To print ${name} itself, escape the dollar in a backtick string: `\\${name}`\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":"#!ek9\ndefines 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}\" (an Integer interpolates directly)\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 text - a ${ inside one is a compile error (E07901)\n      plainText <- \"Plain text, never interpolated\"\n      stdout.println(plainText)\n\n      // To print ${...} itself, escape the dollar in a backtick string\n      literalText <- `Python's f\"{name}\" is EK9's \\${name}`\n      stdout.println(literalText)","migrationContext":"Python developers: f-strings map to EK9 backtick strings. Use ${expression} instead of {expression}. Double-quoted strings have no interpolation - \"${name}\" is a compile error (E07901), where Kotlin, Groovy, Dart, PHP and the shells would interpolate it.","keywords":["backtick","fstring","interpolation","migration","python","safe","string"],"primaryTopics":["Python f-strings to EK9 backticks","string interpolation"],"typicalErrors":[{"error":"E07901","correct":"fullDescription <- `${firstName} ${lastName} (${age})`","incorrect":"fullDescription <- \"${firstName} ${lastName} (${age})\"","explanation":"Only a backtick String interpolates. A double-quoted String holding ${ is a compile error (E07901) - it would otherwise print the characters, not the values. See ek9 -h E07901 for details."}],"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 base Exception that you extend into your own hierarchy (NetworkError extends Exception); it ships no large zoo of built-in ValueError/TypeError-style types\n- A try has a single catch, not several except clauses; the catch may name a specific Exception subtype, or route several subtypes with a dispatcher\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    <- rtn as String: \"default\"\n    rtn: riskyOperation()\n  catch\n    -> ex as Exception\n    rtn: \"recovered\"","ek9Example":"#!ek9\ndefines 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      // The second denominator gives an unset result, so the guard fails and the else runs\n      for denominator in [3.0, 0.0]\n        if safeAnswer <- riskyDivision(10.0, denominator)\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 a base Exception you can extend into your own hierarchy; there is no large built-in zoo of exception types.","keywords":["catch","except","exception","finally","migration","python","try"],"primaryTopics":["Python try/except to EK9 try/catch"],"typicalErrors":[{"error":"E50060","correct":"stderr.println(`Caught: ${ex}`)","incorrect":"stderr.println(`Caught: ${ex.message()}`)","explanation":"An Exception has no message() method; the text is read with ex.reason(), or the exception is converted with the $ operator or plain interpolation. Python's 'except Exception as ex' becomes 'catch' with '-> ex as Exception' on its own line. See ek9 -h E50060 for details."}],"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 builds input sanitisation into the parameter declaration with the 'sanitized' keyword.\n\nPython relies on runtime discipline: cursor.execute with a %s placeholder and a parameter tuple is safe, while an f-string query built from user_input is UNSAFE - and nothing stops you writing the second.\n\nIn EK9 the function that RECEIVES the input declares it sanitized:\n  buildQuery() as pure\n    -> userInput as sanitized String\n    <- rtn as String: `SELECT * FROM users WHERE name = ${userInput}`\n\nThis compiles - it is not a compile error. What 'sanitized' adds is a cleaning step at RUN TIME: the function or method that declares it cleans the parameter the moment it is entered, before any of its code runs, whatever route calls it. Ordinary input passes through unchanged; a detected threat (an injection, a script) is logged and arrives as an UNSET String, so anything built from it - here the query - is unset too and is never run.\n\nCallers pass any String and write nothing extra - the declaration decides, so a caller cannot forget to clean the input. The compiler's part is to guarantee that the cleaning step is always there; it does not track the value afterwards.\n\nSanitized parameters work on functions, methods, constructors, and service endpoints.","ek9Example":"#!ek9\ndefines module qa.security.frompythonsqlsafety\n\n  defines function\n\n    //'sanitized': this function cleans userInput the moment it is entered,\n    //at run time - a detected threat arrives as an UNSET String\n    buildQuery() as pure\n      -> userInput as sanitized String\n      <- rtn as String: `SELECT * FROM users WHERE name = ${userInput}`\n\n  defines program\n\n    SqlSafetyDemo()\n      stdout <- Stdout()\n\n      //Ordinary input passes through unchanged\n      query <- buildQuery(\"Alice\")\n      stdout.println(query)\n\n      //An injection attempt is caught on entry: the parameter arrives unset,\n      //so the query built from it is unset too, and is never run\n      attack <- buildQuery(\"x' OR '1'='1\")\n      stdout.println(`Injection query is set: ${attack?}`)","migrationContext":"Python developers: your parameterised-queries discipline becomes a parameter declaration in EK9. A 'sanitized' parameter is cleaned by the receiving code on every call, so injection input arrives unset instead of relying on every caller to remember.","keywords":["compile time","injection","migration","python","sanitized","security","sql"],"primaryTopics":["sanitized keyword","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  import os\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":"#!ek9\ndefines 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":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. It is a lazy peek: it records only what flows past it. Here the tee is before the sort, which must read every valid item, so validBackup gets all of them; a tee placed after the sort, just before head 10, would record only the 10 items head demands.\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":"#!ek9\ndefines 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  stdout.println(`${emptyList?}`)   // true - empty list IS set\n\n  emptyDict <- Dict() of (String, Integer)\n  stdout.println(`${emptyDict?}`)   // true - empty dict IS set\n\nThis is different from primitives where an uninitialised value is unset:\n  unsetName <- String()\n  stdout.println(`${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":"#!ek9\ndefines 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, case, catch, for, while, guards and boolean and/or) per function. Keep each function focused on one task.","ek9Example":"#!ek9\ndefines 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  // ERROR E01071 there is no continue in EK9\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":"#!ek9\ndefines 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":1023,"category":"Control Flow","question":"What is the difference between :=, :=? and the explicit re-point-and-check in EK9 control flow?","url":"https://ek9.io/qa/QA1023.html","alternatePhrasings":["When should I use := vs :=? in a switch statement?","How do the assignment pre-flows differ in EK9?","What guard operator should I use in EK9 control flow?","How do I re-point a variable and skip a switch when the new value is unset?"],"answer":"EK9 has two assignment pre-flows for EXISTING variables, and one explicit pattern for re-pointing a variable and entering only when the new value is set.\n\n:= (BLIND ASSIGNMENT)\nAlways evaluates the expression. Always assigns, an unset result included. No checks: the body always runs.\n  switch mainValue := getValue()\n    case 1 ...\nUse when: the value is known safe, or a 'with'/'then' condition validates it.\n\n:=? (ASSIGN-IF-UNSET - checks the LEFT side)\nFirst checks whether the target is already set. Only evaluates and assigns the expression if the target is UNSET; a target that is already set keeps its value. The body then runs only if the target is SET.\n  switch mainValue :=? getValue()\n    case 1 ...\nUse when: the variable may already have a value (caching, defaults, priority chains). Avoids re-evaluation.\n\nRE-POINT, THEN CHECK (assign, then run only if the new value is set)\nAssign with ':=' (an unset result OVERWRITES the old value), then check it. In an if or while the check fits in the head: 'if mainValue := getValue() then mainValue?'. A switch, for, try or do head has no condition to carry it, so write the three steps:\n  mainValue := getValue()\n  if mainValue?\n    switch mainValue\n      case 1 ...\n\nDECISION TREE:\n  First time using this variable? -> Use <- (declaration guard)\n  Variable exists, keep it if already set? -> Use :=? (lazy, avoids re-evaluation)\n  Variable exists, take the new value and enter only if it is set? -> ':=' then check it ('then x?', or 'if x?' around the construct)\n  Variable exists, value known safe? -> Use := (blind, no checks)\n\nSee Q74 for the declaration guard (<-). See Q79 for :=? standalone usage. See Q759 for assigning and then checking. See Q1024 for re-point-and-check across control flow. See Q1025 for := vs :=? across control flow.","ek9Example":"#!ek9\ndefines 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      // === RE-POINT, THEN CHECK (assign, then switch only if the new value is set) ===\n      // guardedResult starts SET (1). getUnsetValue() returns an unset Integer.\n      // ':=' still ASSIGNS it, so guardedResult becomes unset and the 'if' skips the switch.\n      guardedResult <- getOne()\n\n      guardedResult := getUnsetValue()\n      if guardedResult?\n        switch guardedResult\n          case 1\n            stdout.println(\"re-pointed matched case 1\")\n          default\n            stdout.println(\"re-pointed hit default\")\n        stdout.println(`re-point kept a value (value: ${guardedResult})`)\n      else\n        stdout.println(\"re-point assigned the unset result, so the switch was skipped\")\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\")\n\n      // lazyResult is now SET, so :=? does not call getZero() and keeps the 1.\n      switch lazyResult :=? getZero()\n        case 1\n          stdout.println(`:=? kept the existing value (value: ${lazyResult})`)\n        default\n          stdout.println(`:=? replaced it (value: ${lazyResult})`)","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 :=? equivalent. EK9: blind assignment (:=), lazy assign-if-unset (:=?) and the declaration guard (<-), plus the explicit 'x := f() then x?' to re-point and check — one 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 do I re-point an existing variable and run a block only when the new value is set?","url":"https://ek9.io/qa/QA1024.html","alternatePhrasings":["How do I assign and check isSet in an if or switch head?","What happens when I assign an unset value in a control-flow head in EK9?","Does ':=' overwrite a variable that is already set with an unset result?","How do I safely assign from a function that might return unset?","What replaced the removed ?= guard in an if or switch head?"],"answer":"Assign with ':=' and check the new value with '?'. ':=' ALWAYS evaluates the right side and ALWAYS assigns the result, an unset result included; the explicit check then decides whether the body runs. What this protects is the BODY: code inside it never sees an unset value. It does not protect the target: an unset result overwrites whatever the target held before. (EK9 has no '?=' operator - it was removed; the check is always written out.)\n\nIN IF (and WHILE): the check fits in the head\n  if data := fetchRecord(id) then data?\n    process(data)\nfetchRecord is called and its result is assigned to data. If data is set, the body executes; if it is unset, the body is skipped (an else branch runs if there is one). With a further condition, parenthesise it: 'then data? and (data > limit)'.\n\nIN SWITCH (and FOR, TRY, DO): the head has no condition, so write three steps\n  value := parseInput(text)\n  if value?\n    switch value\n      case 1 ...\nIf the result is unset, the entire switch (including default) is skipped.\n\nA SET TARGET IS OVERWRITTEN\nIf the target already held a value and the expression returns unset, the target is unset afterwards. To keep an existing value when the new one is missing, use ':=?' (assign only if the target is unset) or assign into a NEW variable with a declaration guard ('if fresh <- fetchRecord(id)').\n\nCOMPARISON:\n':=' always assigns and checks nothing.\n':=' then 'x?' always assigns, then enters the body only if the result is set.\n':=?' checks the target BEFORE evaluating: it assigns only if the target is unset (lazy, avoids unnecessary work), then enters only if the target is set.\n\nSee Q1023 for the operators compared. See Q759 for assigning and then checking. See Q74 for <- declaration guard.","ek9Example":"#!ek9\ndefines module qa.controlflow.repoint.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      // === RE-POINT AND CHECK IN IF: the body only sees a SET value ===\n      existing <- Integer()\n\n      if existing := fetchSetValue() then existing?\n        stdout.println(\"IF re-point with set value: assigned \" + $existing)\n\n      // Reset for next demo\n      existing: Integer()\n\n      if existing := fetchUnsetValue() then existing?\n        stdout.println(`IF re-point body: never printed ${existing}`)\n      else\n        stdout.println(\"IF re-point with unset value: body skipped\")\n\n      // === ':=' OVERWRITES A SET TARGET with an unset result ===\n      held <- fetchSetValue()\n\n      if held := fetchUnsetValue() then held?\n        stdout.println(`Held still: ${held}`)\n      else\n        stdout.println(`Held was 42, now set: ${held?}`)\n\n      // === RE-POINT AND CHECK AROUND A SWITCH: skips the entire switch when unset ===\n      switchControl <- Integer()\n\n      switchControl := fetchUnsetValue()\n      if switchControl?\n        switch switchControl\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      // === RE-POINT AND CHECK AROUND A SWITCH: enters the switch when set ===\n      switchValid <- Integer()\n\n      switchValid := fetchSetValue()\n      if switchValid?\n        switch switchValid\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: 'data = fetch(); if (data != null && data.isValid()) { switch(data) {...} }'. EK9: 'data := fetch()' then 'if data?' around the switch - or, in an if or while head, 'if data := fetch() then data?'. Rust: if let Some(v) = expr — similar concept but no assignment to an 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":["re-point and check in control flow","assign then isSet","unset value handling"],"typicalErrors":[{"error":"E01073","correct":"      if existing := fetchSetValue() then existing?","incorrect":"      if existing != null","explanation":"'null' does not exist in EK9 - assign, then check the new value with '?'. 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, and then assigns the result. The body runs only if the target is SET afterwards - so a target that already held a value enters the body with that value, and an unset result skips it.\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 re-point-and-check in control flow.","ek9Example":"#!ek9\ndefines 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 assign-if-unset","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 first draft of this class had two naming issues, both fixed in the code shown here:\n\n1. The discount method was called 'proc' - too abbreviated. It is now 'applyDiscount'. Method names should describe the action clearly.\n\n2. The local for the discount amount was called 'x' - meaningless. It is now 'discountAmount' (and 'saving' in the program). Local variables should describe what the value represents.\n\nThe rest of the naming was already fine and is unchanged:\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":"#!ek9\ndefines 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, a single class that handled both user validation and notification would be doing too much - two separate concerns.\n\nThe code shown is the split result:\n- A UserValidator class that checks names and emails\n- A Notifier class that sends messages\n- The program holds one of each and calls both - that is 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. For a larger design, compose them together in a coordinator class that holds one of each.","ek9Example":"#!ek9\ndefines 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 its default String form (for example 102.0 - no rounding or padding is applied).\n\nDesign notes:\n- All functions are marked 'as pure' for thread safety and testability\n- A set-check (if discountRate?) handles an unset discount 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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, a single function that validates input, calculates a result and formats output would be too complex. Each concern should be a separate pure function.\n\nThe code shown is the decomposed result:\n- validateAmount: checks the value is usable\n- calculateWithTax: does the arithmetic\n- formatCurrency: 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":"#!ek9\ndefines 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\")\n\n      //An unset amount is rejected by the same check\n      missingAmount <- Float()\n      if validateAmount(missingAmount)\n        stdout.println(formatCurrency(calculateWithTax(missingAmount, taxRate)))\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 the user-creation module:\n\nStructure: The module separates validation (the pure functions isValidName and isValidEmail) from state (the User class). This separation means validation logic can be tested without creating a User.\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 program only creates a User once both validation functions agree: if isValidName(name) and isValidEmail(email) gates the block. There is no null checking and no exception handling for bad input - an unset or malformed value simply fails the condition, because each validator starts with a set-check (name?, email?).\n\nReturn values are declared in the header 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":"#!ek9\ndefines 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() 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\nEvery code path must initialise the return value. The compiler enforces this — there is no way to forget.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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\n    | map with toUpper\n    > stdout","ek9Example":"#!ek9\ndefines 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":"E50060","correct":"      uppercased <- cat names\n        | map with toUpper\n        | collect as List of String","incorrect":"      uppercased <- names.map(toUpper)","explanation":"A List has no map() method, so the call is not resolved (E50060). EK9 streams use pipe syntax with named functions, not method chaining. Write 'cat source | map with functionName | collect as Type'. See ek9 -h E50060 for details."}],"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","Collect filtered stream results into a new List.","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 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?\n\n    minLength <- 4\n    rtn: name.length() > minLength\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\n    | filter by predicate\n    | collect as List of T\n  count <- matches.length()","ek9Example":"#!ek9\ndefines 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":"Java: .collect(Collectors.toList()). Python: list(filter(...)). EK9 uses 'cat source | filter by fn | collect as Type' instead of loops with conditionals.","keywords":["capture","coder","collect","filter","list","materialise","pipeline","result","stream"],"primaryTopics":["stream filter","collect as list","collect as","stream materialisation"],"typicalErrors":[{"error":"E50060","correct":"      longNames <- cat names\n        | filter by isLongName\n        | collect as List of String","incorrect":"      longNames <- names.filter(isLongName)","explanation":"A List has no filter() method, so the call is not resolved (E50060). EK9 has no method-chained collection operations: use a stream pipeline, cat source | filter by fn | collect as Type. See ek9 -h E50060 for details."}],"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 plain data carriers (public fields, copied by value) — a good fit for search parameters.\n\n2. Define the result type as a record:\n   SearchResult with title as String and score as Float\n   Give it 'default operator' to get comparison, $ and the other operators generated.\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 byScore\n     | head maxResults\n     | collect as List of SearchResult\n\nThe pipeline expresses the search algorithm declaratively.\n\n5. Use a guard, then test for emptiness, in the caller:\n   if results <- performSearch(criteria)\n     if results is not empty\n       showResults(results)\n\nThe guard only skips an absent or unset result. A collection is SET as soon as it exists, even when empty, so 'no results found' is the 'results is not empty' test inside the guard (or 'length results > 0').\n\nKey EK9 choices:\n- Records for data (public fields; operators from 'default operator')\n- Pure functions for logic (testable, composable)\n- Streams for collection processing (declarative, no loops)\n- Guards to skip an absent or unset result; an emptiness test for 'nothing found'","ek9Example":"#!ek9\ndefines 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()\nMultiple case values on one line replace fallthrough.\n\n4. Use declaration guards for conditional logic:\n   if result <- computeSomething()\n     process(result)\nGuards combine computation and null-safety checking.\n\n5. Use records for data transfer:\n   A record gets equality, string representation and hashcode from a one-line 'default operator', with no hand-written boilerplate. Without it a record has none of these.\n\nPriority order: extract pure functions first, then replace loops with streams, then clean up conditionals.","ek9Example":"#!ek9\ndefines 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 guards.","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 :=? (assign-if-unset) 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 :=\nWith :=, every source would be called and the last one always wins.\nWith :=?, 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":"#!ek9\ndefines 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":["assign-if-unset","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 assign-if-unset 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()     assign-if-unset — fills name if unset, enters if SET","ek9Example":"#!ek9\ndefines module qa.controlflow.declare.vs.assignifunset\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 assign-if-unset"],"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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 :=? (assign-if-unset) which Go does not have. It only assigns if the target is currently unset.","ek9Example":"#!ek9\ndefines 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 assign-if-unset 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":"#!ek9\ndefines 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  defines class\n    UniqueNames extends List of String    // ERROR E05030 closed - cannot extend\n\nAFTER (composition):\n  defines class\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\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"Car with trait of Describable","incorrect":"Car extends Engine with trait of Describable","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- 'orderItems' is a List of Float holding prices, so a name such as 'orderPrices' would say what the values are\n- The loop variable 'orderItem' then reads better as 'orderPrice', because it is a price and not an item\n- Little else needs improving: there are no abbreviations and no vague method names, which is what the compiler's naming checks reward\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 / 100.0\n\n      default operator ?\n\nUsage:\n  discount <- Percentage(15.0)\n  asFloat <- #^ discount     the fraction 0.15\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":"#!ek9\ndefines 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 / 100.0\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 a Float rate (0.15) 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 / 100.0","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 - its sameness - with a matching #? that hashes the same fields. The compiler then GENERATES <, >, <=, >=, == and <> from <=>; you never write them (writing or requesting one is E07181), so they can never disagree with <=>. All comparison operators are 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        <- rtn as Integer: #?level\n\n      default operator\n\nA hand-written <=> needs a hand-written #? (E07184); 'default operator' then supplies the rest ($, ?).\n\nOnce <=> is 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 record has <=>. For a different ordering (say by label), give sort a comparator function instead of writing a second <=>.\n\nSee Q1050 for coalescing operators on a class. See Q239 for comparison ordering. See Q1051 for operators on traits.","ek9Example":"#!ek9\ndefines 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      //A hand-written <=> needs a hand-written #? hashing the same field\n      operator #? as pure\n        <- rtn as Integer: #?level\n\n      //<, >, <=, >=, == and <> are generated from <=>; this adds $ and ?\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. (functools.total_ordering). Rust: derive Ord. EK9: define <=> (and #?); the compiler generates <, >, <=, >=, == and <> from it.","keywords":["E07181","E07184","comparison","operator","record","sameness","sort","spaceship"],"primaryTopics":["comparison operators on records","sortable records"],"typicalErrors":[{"error":"E07181","correct":"      default operator","incorrect":"      operator < as pure\n        -> other as Priority\n        <- rtn as Boolean: (this <=> other) < 0\n\n      default operator","explanation":"The < operator on a record with <=> is generated from <=> - writing it by hand is E07181 (COMPARISON_GENERATED_FROM_SAMENESS). Delete it: <, >, <=, >=, == and <> all come from <=>. See ek9 -h E07181 for details."},{"error":"E07184","correct":"      operator #? as pure\n        <- rtn as Integer: #?level","incorrect":"      default operator #?","explanation":"A hand-written <=> needs a hand-written #? that hashes what <=> compares (here, level). A defaulted #? would hash every field and disagree with the sameness, so it is E07184. See ek9 -h E07184 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_add_operator","intent":"operators","description":"Oracle can add the <=> operator (and its #?) to a record; the compiler generates <, <=, >, >=, == and <> from it."}}
{"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 declares its sameness, the <=> operator, with a matching #?. The compiler generates <, >, <=, >=, == and <> from <=> (writing them by hand is E07181), and the coalescing operators are answered by <=>.\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 one operand is unset, the other (set) operand is returned; only when both are unset is the result unset.\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":"#!ek9\ndefines 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      //<, >, <=, >=, == and <> are generated from <=> - the coalescing operators use it\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. A trait can declare the SAMENESS of every type that implements it: a concrete <=> (or ==) plus a matching #?. Sameness must be concrete where it is declared - a bodiless (abstract) <=> in a trait is E07183 - so the trait writes the body over its own abstract accessors, and implementers supply those accessors, not an override of <=>.\n\n  defines trait\n    Rankable\n      rank() as pure\n        <- rtn as Integer?\n\n      operator <=> as pure\n        -> other as Rankable\n        <- rtn as Integer: rank() <=> other.rank()\n\n      operator #? as pure\n        <- rtn as Integer: #?rank()\n\nAny class 'with trait of Rankable' implements rank() and inherits the sameness. The compiler generates <, >, <=, >=, == and <> from the trait's <=> (writing them is E07181), and the sameness is sealed: an implementer may not redeclare <=>, == or #? (E07182/E07184). The trait contract guarantees that all Rankable objects can be compared, sorted and used with <? and >?.\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\nA different ordering for one use (say by name) is a comparator function given to sort, never a second <=>.\n\nNote: override is required when implementing the trait's accessor in a class (override rank()).\n\nSee Q1049 for comparison operators on records. See Q1050 for coalescing on classes. See Q210 for trait delegation.","ek9Example":"#!ek9\ndefines module qa.operators.operatorsontrait\n\n  defines trait\n\n    Rankable\n      rank() as pure\n        <- rtn as Integer?\n\n      //The sameness, written once over the trait's own abstract accessor.\n      //<, >, <=, >=, == and <> with any Rankable are generated from it.\n      operator <=> as pure\n        -> other as Rankable\n        <- rtn as Integer: rank() <=> other.rank()\n\n      operator #? as pure\n        <- rtn as Integer: #?rank()\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      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: a trait declares a concrete <=> and #? over its abstract accessors; implementers supply the accessors.","keywords":["E07183","abstract","comparison","contract","hashcode","operator","sameness","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."},{"error":"E07183","correct":"      operator <=> as pure\n        -> other as Rankable\n        <- rtn as Integer: rank() <=> other.rank()","incorrect":"      operator <=> as pure\n        -> other as Rankable\n        <- rtn as Integer?","explanation":"Sameness must be concrete where it is declared: a bodiless (abstract) <=> in a trait is E07183 (SAMENESS_MUST_BE_CONCRETE). Write the body over the trait's own abstract accessor (rank()); implementers supply rank(), not an override of <=>. See ek9 -h E07183 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    Hue\n      red as Integer: 0\n      green as Integer: 0\n      blue as Integer: 0\n\n      Hue()\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\nUsage:\n  crimson <- Hue(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 Hue, 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":"#!ek9\ndefines module qa.operators.promoterecordstring\n\n  defines record\n\n    Hue\n      red as Integer: 0\n      green as Integer: 0\n      blue as Integer: 0\n\n      Hue()\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 <- Hue(220, 20, 60)\n      skyBlue <- Hue(135, 206, 235)\n\n      //Explicit promote\n      asString <- #^ crimson\n      stdout.println(asString)\n\n      //$ for display\n      stdout.println(`Hue: ${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 Hue: Hue(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). Whatever #? hashes, == must compare: two objects that are equal must have the same hash, so a hand-written #? needs a hand-written == over exactly the same 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":"#!ek9\ndefines 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 #? combining all the fields; == compares exactly the same 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: firstName == other.firstName and lastName == other.lastName and 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. Using := on a new name is E50001 (not resolved).\n\n2. Recommendation (not a compile error): 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 - the code compiles without it, but pure gives the compiler and readers a guarantee.\n\n3. Change the return variable type initialisation.\n   <- rtn as String?  becomes  <- rtn as String: String()\n   Reason: a return value must be initialised on every path. Without the initialiser the compiler reports E08050 (return value is not always initialised) because the if has no else. String() creates an unset String.\n\nEach fix is one small change. Do not restructure the code or add features — just fix the errors (fixes 1 and 3) and apply the recommendation (fix 2) only if asked.","ek9Example":"#!ek9\ndefines 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 from it ==, <>, <, <=, >, >=), #?, $ 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        -> other as Account\n        <- rtn as Integer: accountId <=> other.accountId\n\n      operator #? as pure\n        <- rtn as Integer: #? accountId\n\n      operator $ as pure\n        <- rtn as String: `Account-${accountId}`\n\n      override operator ? as pure\n        <- rtn as Boolean: accountId? and balance?\n\nHere the account's identity is its accountId (not balance): <=> compares it and #? hashes it, and $ shows a formatted string. Default operator would compare and hash both fields and show both in the string.\n\n#? GOES WITH SAMENESS: a hand-written #? needs a hand-written sameness (<=>, or == for a type that cannot be ordered) that compares what it hashes, and vice versa - a #? on its own, or 'default operator #?' beside a hand-written <=>, is E07184. ==, <>, <, <=, >, >= are generated from the sameness, never written (E07181).\n\nYOU CAN MIX: write the operators you want custom FIRST, then 'default operator' LAST:\n  operator $ as pure\n    <- rtn as String: `Custom: ${name}`\n  default operator\n\n'default operator' must be the last item (anything after it is E01086), and it generates only the operators not already written - here everything except $. Write 'operator $', not 'override operator $': there is no parent $ to override (E05110).\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":"#!ek9\ndefines 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 sameness, 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      //Sameness by accountId only, not balance: == and <> are generated from <=>\n      operator <=> as pure\n        -> other as Account\n        <- rtn as Integer: accountId <=> other.accountId\n\n      //A hand-written sameness needs a hand-written #? hashing the same field\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":"E07181","correct":"      default operator","incorrect":"      operator < as pure\n        -> other as Address\n        <- rtn as Boolean: street < other.street\n\n      default operator","explanation":"For simple value types, 'default operator' generates all standard operators in one line. Mixing in a hand-written < does not work: < is generated from the type's <=> (here the defaulted one), so writing it by hand is E07181 and could only disagree with <=>. Manual implementation is for custom behaviour - and then the sameness (<=>) and #? are written together. See ek9 -h E07181 for details."}],"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 the _string() operator and returns a String\n- `${age}` inside backticks is interpolation, embedding the value in the 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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, and what is the difference between them?","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?","Why does name ?? \"default\" not give me the default when name is unset?","Should I use ?? or ?: to default an unset value in EK9?"],"answer":"EK9 has no null. Its tri-state model has THREE states - ABSENT (no object at all), present but UNSET (an object with no meaningful value, e.g. String()), and SET - and the two coalescing operators test DIFFERENT things.\n\n?? NULL COALESCING - tests ABSENCE only:\n  result <- name ?? \"default\"\nReturns name whenever an object is present, EVEN IF IT IS UNSET. Only an ABSENT left operand gives \"default\". A present-but-unset value passes straight through, so the result is still unset.\n\n?: COALESCE (ELVIS) - tests SET-NESS (absent or unset):\n  result <- name ?: \"default\"\nReturns name only if it is SET. If name is unset (or absent) the result is \"default\".\n\nTHE DIFFERENCE IN ONE EXAMPLE\n  name <- String()              present, but UNSET\n  a <- name ?? \"stranger\"       a is UNSET (?? saw an object, so it returned it)\n  b <- name ?: \"stranger\"       b is \"stranger\"\n\nRULE OF THUMB\nTo give an UNSET value a default, use ?: (or :=? to assign in place). Values you declare and assign in EK9 are present, so ?? almost never does what a default needs - it is for a left operand that may not exist at all.\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 absence only\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  stdout.println(`${name?}`)      // false (unset)\n  name := \"Alice\"                 // now SET\n  stdout.println(`${name?}`)      // true\n\nIf BOTH sides of ?: are unset, the result is unset.\n\nSee Q243 for all coalescing operators. See Q898 for ? is not ternary. See Q883 for tri-state semantics.","ek9Example":"#!ek9\ndefines module qa.operators.coalescingisset\n\n  defines program\n\n    CoalescingDemo()\n      stdout <- Stdout()\n\n      //An UNSET String: the object is present, it just has no value\n      name <- String()\n      stdout.println(`name is set: ${name?}`)\n\n      //?? only tests for ABSENCE - name is present, so the unset value passes straight through\n      passedThrough <- name ?? \"stranger\"\n      stdout.println(`?? result is set: ${passedThrough?}`)\n\n      //?: tests SET-NESS - name is unset, so the default is used\n      defaulted <- name ?: \"stranger\"\n      stdout.println(`Hello ${defaulted}`)\n\n      //Set the name\n      name := \"Alice\"\n      stdout.println(`name is set: ${name?}`)\n\n      //Now name is set, so both operators return it\n      viaNullCoalescing <- name ?? \"guest\"\n      stdout.println(`Hello ${viaNullCoalescing}`)\n      viaElvis <- name ?: \"visitor\"\n      stdout.println(`Hello ${viaElvis}`)","migrationContext":"Java: ternary with null check. Kotlin: ?: (elvis) checks null. JavaScript/C#/Swift: ?? (nullish coalescing) checks null. EK9: ?? checks only that an object is PRESENT (an unset value passes through); ?: checks that the value is SET - use ?: to default an unset value. EK9 has no null.","keywords":["absent","coalescing","default","elvis","isset","null","operator","unset"],"primaryTopics":["?? checks absence not isSet","?: elvis operator defaults unset values"],"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?","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. 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\nRECORDS VS CLASSES\nRecords can have constructors and operators but NOT methods. Classes can have constructors, methods and operators. Records are for DATA (fields visible, generated operators); classes are for BEHAVIOUR (fields private, explicit methods).\n\nSee Q97 for record basics. See Q98 for record operators. See Q93 for class basics.","ek9Example":"#!ek9\ndefines 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    coordinateSum() 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      total <- coordinateSum(point)\n      stdout.println(`Sum: ${total}`)","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","accessor","class","field","private","public","record","visibility"],"primaryTopics":["record fields are public","class fields are private","record field visibility","class field visibility"],"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- Preferably be marked 'as pure' (recommended; a non-pure function is also accepted by 'filter by')\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":"#!ek9\ndefines 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":"E11064","correct":"minimumPrice <- 50.0\n      rtn: price > minimumPrice","incorrect":"rtn: price > 50.0","explanation":"A bare literal inside a comparison is a magic literal. Give the threshold a meaningful name (minimumPrice) and compare against the name. See ek9 -h E11064 for details."}],"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- Preferably be marked 'as pure' (recommended; a non-pure function is also accepted by 'map with')\n\nSee Q1061 for filter functions. See Q1034 for chained transforms.","ek9Example":"#!ek9\ndefines 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":"E50060","correct":"<- rtn as String: name.upperCase()","incorrect":"<- rtn as String: name.toUpperCase()","explanation":"String has no toUpperCase() method in EK9; the method is upperCase(). Write the transform as a named pure function and pass it to map with by name. See ek9 -h E50060 for details."}],"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":"#!ek9\ndefines 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":"E11064","correct":"passingMark <- 60\n      rtn: score >= passingMark","incorrect":"rtn: score >= 60","explanation":"A bare literal inside a comparison is a magic literal. Name the passing mark (passingMark <- 60) and compare against the name. See ek9 -h E11064 for details."}],"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    ->\n      price as Float\n      quantity as Integer\n      discount as Float\n      taxRate as Float\n    <- rtn as Float: ...\n\n  formatInvoice() as pure\n    ->\n      price as Float\n      quantity as Integer\n      discount as Float\n      taxRate as Float\n    <- rtn as String: ...\n\n  isValidOrder() as pure\n    ->\n      price as Float\n      quantity as Integer\n      discount as Float\n      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":"#!ek9\ndefines 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":"E11062","correct":"order <- OrderLine(price: 29.99, quantity: 3, discount: 0.10, taxRate: 0.20)","incorrect":"order <- OrderLine(29.99, 3, 0.10, 0.20)","explanation":"A call with 4 or more positional arguments hides what each value means. Group the values in a record and pass them by name (or pass the record itself) - the names then document the call."}],"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: it is the type's sameness, and the compiler generates ==, <>, <, <=, > and >= from it. Writing any of those for the type itself is an error (E07181). Add a hand-written #? that hashes what <=> compares (E07184 otherwise), $ for string display, and ? because a class with properties needs it (E07235 otherwise).\n\ndefines class\n  Product\n    productName as String: String()\n    price as Float: 0.0\n\n    Product()\n      ->\n        productName as String\n        price as Float\n      this.productName :=: productName\n      this.price :=: price\n\n    override operator ? as pure\n      <- rtn as Boolean: productName? and price?\n\n    operator <=> as pure\n      -> other as Product\n      <- rtn as Integer: price <=> other.price\n\n    operator #? as pure\n      <- rtn as Integer: #? price\n\n    operator $ as pure\n      <- rtn as String: `${productName}: ${price}`\n\nNow you can sort, and use every comparison:\n  cat products | sort > stdout\n  cheapest <- first <? second\n\nFor a different ordering (by name, say) write a comparator function and use 'sort by', never a second <=>.\n\nSee Q1049 for comparison on records. See Q1047 for hashcode. See Q1055 for default operator.","ek9Example":"#!ek9\ndefines 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      //== <> < <= > >= are all generated from <=>; #? hashes what <=> compares\n      operator #? as pure\n        <- rtn as Integer: #? 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 <=> once; the compiler generates ==, <>, <, <=, >, >= from it.","keywords":["class","code","coder","comparison","operator","sort","write"],"primaryTopics":["write sortable class","comparison operators"],"typicalErrors":[{"error":"E07181","correct":"      operator <=> as pure\n        -> other as Product\n        <- rtn as Integer: price <=> other.price","incorrect":"      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: price == other.price","explanation":"The <=> operator is the type's sameness and the compiler generates ==, <>, <, <=, > and >= from it. Writing == for the type itself is error E07181. See ek9 -h E07181 for details."}],"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?","Does EK9 have list comprehension like Python?","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":"No. EK9 has NO list comprehensions. This syntax does not exist and is invalid in EK9:\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":"#!ek9\ndefines 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":["ai","comprehension","filter","list","map","mistake","pipeline","python","stream","transform","wrong"],"primaryTopics":["no list comprehensions","use stream pipeline","no list comprehension","stream pipelines","collection building"],"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":"#!ek9\ndefines 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 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 you use 'default operator' alongside a manual operator ?. Write the manual one BEFORE 'default operator' (which must be the last item in the class body - anything after it is error E01086); 'default operator' then generates only what you did not write:\n\n  override operator ? as pure\n    <- rtn as Boolean: name? and age?\n\n  default operator\n\nThe 'override' keyword is required on an operator that already exists in a parent type - and only there. For operator ?, it is always required because every type inherits it.\n\nWhich operators need 'override':\n  override operator ?    isSet - always (every type inherits it)\n  override operator $    string - only if a PARENT type declares $. Written beside 'default operator' with no parent $, it takes no 'override' (E05110 'has no parent version').\nSameness operators are different: '==' or '<=>' is declared once per type hierarchy, and a subtype inherits it and can NOT redefine it, with or without 'override' (E07182).\n\nSee Q574 for override mechanics. See Q877 for the ? operator. See Q1055 for default vs manual operators.","ek9Example":"#!ek9\ndefines 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: personName? 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 E01086 when I put operator ? after default 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 own operators BEFORE it; 'default operator' then generates only the ones you did not write. The 'override' keyword is required on operator ? because every class inherits ? from its base type.\n\nWHAT GOES WRONG:\n  default operator\n  operator ? as pure           WRONG: E01086, nothing may follow 'default operator'\n    <- rtn as Boolean: host?\n\nAdding 'override' there does not help - it is still E01086. Move the operator above 'default operator'. And placed above it WITHOUT 'override', operator ? is E05120 ('override' required).\n\nAPPROACH 3 - no default, all manual:\n  override operator ? as pure  ? is inherited from the base type, so override is still required\n    <- rtn as Boolean: host?\n  operator $ as pure           no override - no parent declares $\n    <- rtn as String: host\n\nSee Q1068 for the E05120 fix. See Q116 for what default operator generates. See Q1055 for default vs manual.","ek9Example":"#!ek9\ndefines 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' is mandatory on an operator a parent type already has (always for ?), and 'default operator' must come last, after your own operators (E01086 otherwise).","keywords":["E01086","E05120","combine","default","duplicate","operator","override"],"primaryTopics":["default operator with override","E01086 default operator must be last"],"typicalErrors":[{"error":"E05120","correct":"      override operator ? as pure\n        <- rtn as Boolean: host?","incorrect":"      operator ? as pure\n        <- rtn as Boolean: host?","explanation":"A manual operator ? placed before 'default operator' still replaces the ? every class inherits from its base type, so it needs 'override'. (Placing it AFTER 'default operator' is error E01086, with or without 'override'.)"}],"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 only when a PARENT type declares it:\n  override operator $ as pure    if a parent has $ (beside 'default operator' with no parent $, it takes no override - E05110)\n\nNEVER overridden - sameness is declared once per type hierarchy:\n  '==' or '<=>'                  a subtype inherits it and cannot redefine it (E07182)\n  '<>', '<', '<=', '>', '>='     always generated from the sameness, never written (E07181)\n  '#?'                           goes with the sameness: a subtype inherits it with '==' / '<=>' (E07184)\n\nDoes NOT need override (no parent version exists):\n  operator $ as pure             if no parent declares $\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\nSee Q1068 for the E05120 fix. See Q1069 for mixing default operator with overrides.","ek9Example":"#!ek9\ndefines 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: accountId?","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        ->\n          first as A\n          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        ->\n          first as A\n          second as B\n        this.first :=: first\n        this.second :=: second\n\n      default operator ?\n\nThe parameterised (inferred) constructor must be public (E06060 rejects a private or protected one). The default constructor may be declared 'default private Pair()', but then code outside the type cannot create an empty one such as 'Pair() of (String, Integer)' (E06180); keep it public when callers need that.\n\nSee Q194 for generic class basics. See Q196 for multi-parameter generics. See Q116 for default operator.","ek9Example":"#!ek9\ndefines 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 (public, or 'default private' to stop outside code creating an empty one) 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      //Constructor 2: parameterised (public — used by code)","explanation":"Generic types require 2 constructors: a default (no-arg) and a parameterised one. Add 'default Pair()' (or 'default private Pair()' if outside code must not create an empty one) for the default constructor. See ek9 -h E06040 for details."}],"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":"An abstract function's return value must be declared with ? (unset marker). It cannot be left bare, and it cannot be given a default value either.\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 (? marker):\n  transformer() as abstract\n    -> input as String\n    <- rtn as String?\n\nNOT A FIX (default value, E07100):\n  transformer() as abstract\n    -> input as String\n    <- rtn as String: String()    // ERROR E07100\n\nAn initialiser on the return value counts as an implementation, and an abstract function may not have one: E07100 (defined as default/abstract but an implementation has been provided). The '?' form is the only one that works; the functions that implement the abstract one decide what value to return.\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.","ek9Example":"#!ek9\ndefines 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 declared with ? (an initialiser counts as a body, E07100).","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). Use '<- rtn as String?'; a default initialiser is not allowed either (E07100)."},{"error":"E07100","correct":"    transformer() as abstract\n      -> input as String\n      <- rtn as String?","incorrect":"    transformer() as abstract\n      -> input as String\n      <- rtn as String: String()","explanation":"An initialiser on an abstract function's return value counts as an implementation, so it triggers E07100 - defined as default/abstract but an implementation has been provided. Declare the return as 'String?' instead."}],"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  smallest <- cat scores\n    | sort\n    | head 3\n    | collect as List of Integer\n\nDESCENDING (custom comparator):\n  largest <- cat scores\n    | sort by descending\n    | head 3\n    | 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    ->\n      left as Integer\n      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  largest <- cat scores\n    | sort\n    | tail 3\n    | collect as List of Integer\n\nSee Q979 for sort by examples. See Q120 for collection sorting. See Q235 for complete stream operations.","ek9Example":"#!ek9\ndefines 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        ->\n          studentName as String\n          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":"#!ek9\ndefines 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":"#!ek9\ndefines 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. Because the object is only partly set, its ? is false and its $ is unset: interpolating it into a string prints nothing, so check individual fields (here phoneIsSet()) instead. To copy only SET fields, use :~: merge instead.\n\nSee Q1075 for full copy. See Q1077 for merge. See Q1058 for overview.","ek9Example":"#!ek9\ndefines 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      phoneIsSet() as pure\n        <- rtn as Boolean: phone?\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 set?: ${partial?}`)\n      stdout.println(`Source phone set?: ${partial.phoneIsSet()}`)\n\n      //Copy transfers everything — including unset phone\n      copy <- UserProfile()\n      copy :=: partial\n      stdout.println(`Copy set?: ${copy?}`)\n      stdout.println(`Copy phone set?: ${copy.phoneIsSet()}`)","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. A partly set object such as the update is itself not set (its ? is false), so its $ is unset and interpolating it prints nothing; test it with ? instead.\n\nSee Q1075 for :=: copy. See Q1058 for overview.","ek9Example":"#!ek9\ndefines 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 set?: ${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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"To compare two records by PRICE, compare the price fields - a record's fields are public:\n  byPrice <- itemA.price <=> itemB.price\n  if itemA.price > itemB.price\n    stdout.println(`${itemA.name} costs more`)\n\n'default operator' also gives the whole record <=> and every comparison derived from it (==, <>, <, <=, >, >=), but that ordering compares the fields in declaration order - name first - so itemA <=> itemB is a NAME comparison here (Keyboard before Mouse gives -2), not a price comparison. Use the whole-record operators when that natural ordering is what you want; compare the field (or write a comparator function and use 'sort by') when you want a different key.\n\nSee Q1085 for sorting records. See Q1083 for equality checking.","ek9Example":"#!ek9\ndefines module qa.operators.comparetworecords\n\n  defines record\n\n    Product\n      name as String: String()\n      price as Float: 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      //Compare by price: a record's fields are public, so compare them directly\n      byPrice <- itemA.price <=> itemB.price\n      stdout.println(`Price compare: ${byPrice}`)\n\n      if itemA.price > itemB.price\n        stdout.println(`${itemA.name} costs more`)\n      else\n        stdout.println(`${itemB.name} costs more`)\n\n      //The whole-record <=> from 'default operator' compares the fields in\n      //declaration order - name first - so it is NOT a price comparison\n      byRecord <- itemA <=> itemB\n      stdout.println(`Record compare: ${byRecord}`)\n\n      sameProduct <- itemA == itemB\n      stdout.println(`Same product: ${sameProduct}`)","migrationContext":"Java: implements Comparable<T>, compareTo(), or Comparator.comparing(Product::price). Python: __lt__, __eq__ or functools.total_ordering; key=lambda p: p.price. Rust: impl Ord. Go: manual comparison. EK9: 'default operator' gives a field-order <=> (and the comparisons derived from it); compare a field directly, or write your own operator <=>, for another ordering.","keywords":["<=>","compare","comparison","greater","less","ordering","record"],"primaryTopics":["<=> comparison operator","record ordering"],"typicalErrors":[{"error":"E50060","correct":"byRecord <- itemA <=> itemB","incorrect":"byRecord <- 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":"#!ek9\ndefines 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 Q1088 for checking if records are set.","ek9Example":"#!ek9\ndefines 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 by one field with a comparator function given to sort:\n  bySalary() as pure\n    ->\n      left as Employee\n      right as Employee\n    <- rtn as Integer: left.salary <=> right.salary\n\n  sorted <- cat staff | sort by bySalary | collect as List of Employee\n\nA plain '| sort' uses the type's own <=>, and 'default operator' generates <=> across all fields in declaration order (name first here). A different ordering for one use is a comparator function given to 'sort by', never a second <=>.\n\nSee Q1073 for descending sort. See Q1082 for basic comparison.","ek9Example":"#!ek9\ndefines module qa.operators.sortrecordsbyfield\n\n  defines record\n\n    Employee\n      name as String: String()\n      salary as Float: 0.0\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 function\n\n    bySalary() as pure\n      ->\n        left as Employee\n        right as Employee\n      <- rtn as Integer: left.salary <=> right.salary\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 one field: a comparator function given to sort\n      sorted <- cat staff | sort by bySalary | 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 by comparator | 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 by bySalary | collect as List of Employee","incorrect":"sorted <- staff.sort()","explanation":"EK9 List has no .sort() method; sort via a stream pipeline 'cat list | sort by comparator | 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":"Sort by the price, then take the first:\n  cheapest <- cat products | sort by byPrice | head 1 | collect as List of Product\n\nwhere byPrice is a pure comparator function:\n  byPrice() as pure\n    ->\n      left as Product\n      right as Product\n    <- rtn as Integer: left.price <=> right.price\n\nFor the maximum, sort by a descending comparator (right.price <=> left.price) and take head 1. A plain 'sort' uses the record's own <=> from 'default operator', which compares the fields in declaration order (name first) - it only finds the cheapest if price is the first field, so name the key with 'sort by'.\n\nSee Q1085 for sorting records. See Q1073 for descending sort.","ek9Example":"#!ek9\ndefines module qa.operators.findcheapestproduct\n\n  defines record\n\n    Product\n      name as String: String()\n      price as Float: 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    byPrice() as pure\n      ->\n        left as Product\n        right as Product\n      <- rtn as Integer: left.price <=> right.price\n\n    byPriceDescending() as pure\n      ->\n        left as Product\n        right as Product\n      <- rtn as Integer: right.price <=> left.price\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 by price ascending, take first\n      cheapest <- cat products | sort by byPrice | head 1 | collect as List of Product\n      stdout.println(`Cheapest: ${cheapest}`)\n\n      //Find most expensive: sort by price descending, take first\n      priciest <- cat products | sort by byPriceDescending | head 1 | collect as List of Product\n      stdout.println(`Priciest: ${priciest}`)\n\n      //A plain 'sort' uses the record's own <=> from 'default operator',\n      //which compares the fields in declaration order - name first\n      firstByName <- cat products | sort | head 1 | collect as List of Product\n      stdout.println(`First by name: ${firstByName}`)","migrationContext":"Java: Collections.min(list, Comparator.comparing(Product::price)). Python: min(products, key=lambda p: p.price). Rust: iter().min_by_key(). EK9: cat list | sort by byPrice | 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 by byPrice | head 1 | collect as List of Product","incorrect":"      cheapest <- products.min()","explanation":"EK9 has no .min() method. Use a stream pipeline: sort by a price comparator, then head 1 to get the cheapest."}],"companions":[]}
{"id":1088,"category":"Operators and Expressions","question":"What does default ? report for a partially-initialised record?","url":"https://ek9.io/qa/QA1088.html","alternatePhrasings":["I have a contact with only a name filled in — is it still set?","In Java I'd check each field for null separately. Show the EK9 ALL-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","Check if all fields in a record have been set.","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":"A partially-initialised object is UNSET under default ?:\n  partial <- ContactInfo(\"Alice\")\n  if not partial?\n    stdout.println(\"not fully set\")\n\nWith 1 of 3 fields set, default ? returns false - it uses ALL-field (flattened-aggregate) semantics, so a single unset field - own or inherited - collapses the whole object. An unset object has no $ text either, so interpolating it (`${partial}`) prints nothing at all - test with ? first. Write 'override operator ?' if a partially-filled object should count as set.\n\nSee Q1089 for custom override operator ?.","ek9Example":"#!ek9\ndefines 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      //Interpolating the whole object prints nothing: $ of an unset object is unset\n\n      //default ? uses ALL-field semantics: only 1 of 3 is set, so this is FALSE\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 ? requires EVERY field to be set; override ? for custom logic such as partial-tolerant checks.","keywords":["?","check","field","fields","individual","initialised","isSet","operator","partial","record","set","unset","validate"],"primaryTopics":["partial set status","ALL-field semantics","? operator","isSet checking"],"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":"#!ek9\ndefines 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\n'default operator' alone already requires ALL fields. Add 'override operator ?' before it when you need different logic - for example treating a partially-filled object as set.\n\nSee Q1069 for mixing default with overrides. See Q1088 for default ?.","ek9Example":"#!ek9\ndefines 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 ? (all-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":"Coalesce (Elvis):\n  displayName <- userName ?: \"Guest\"\n\nReturns left if set, right otherwise. ?: and ?? are NOT synonyms: ?? only replaces an ABSENT left operand, so an unset value such as String() passes straight through it - use ?: to default an unset value (see Q1059). See Q243 for all coalescing operators, Q1092 for coalescing min/max.","ek9Example":"#!ek9\ndefines 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 (EK9's ?? only tests absence, so it does not default an unset value).","keywords":["?:","??","coalescing","default","elvis","fallback","unset"],"primaryTopics":["?: coalesce (Elvis)","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 Q243 for all coalescing operators. See Q1091 for ?: elvis.","ek9Example":"#!ek9\ndefines 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, Q243 for all coalescing operators.","ek9Example":"#!ek9\ndefines 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\nJSON:\n  json <- $$cust\n  stdout.println(`JSON: ${json}`)\n\nDefault operator $ generates ClassName(field=value) format and works directly in backtick interpolation. Default operator $$ generates a JSON object from the fields. See Q908 for $ string operator, Q887 for $$ JSON operator.","ek9Example":"#!ek9\ndefines 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}`)\n\n      //$$ JSON representation\n      json <- $$cust\n      stdout.println(`JSON: ${json}`)","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 (comparison goes field by field in declaration order, so name decides before price). It does not generate :~: (merge) or :^: (replace) - write those by hand if needed. See Q949 for default operator rules, Q1098 for overriding individual operators after default.","ek9Example":"#!ek9\ndefines 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      //<=> compares fields in declaration order: name first, so Widget is after Gadget\n      if a > c\n        stdout.println(\"Widget sorts after Gadget (name is compared first)\")\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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\nThe reverse operand order of 'contains' (fruits contains \"banana\" asks the same membership question). Use 'not in' for negation. Returns Boolean. See Q1099 for contains (substring), Q1102 for is in with ranges.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 and sqrt work on Integer and Float (sqrt always returns a Float). See Q240 for arithmetic operators.","ek9Example":"#!ek9\ndefines 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\n      default operator ?\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":"#!ek9\ndefines 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":"E50200","correct":"defines component\n\n    Logger","incorrect":"defines class\n    @Component Logger","explanation":"EK9 has no annotations. '@' introduces a compiler directive such as @Error or @Test, and '@Component' is not one, so it is rejected as an unsupported directive (E50200). Use 'defines component' to declare DI-managed types. See ek9 -h E50200 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_new_construct","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":"#!ek9\ndefines 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\nBecause EVERY field here has an initialiser (e.g., theme <- \"dark\"), the component is SET immediately after construction - default ? requires all fields to be set.\n\nSee Q1088 for default operator ? semantics. See Q1105 for defining components.","ek9Example":"#!ek9\ndefines 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 — all-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  defines component\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":"#!ek9\ndefines 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  defines component\n    Counter\n      count <- 0\n      increment()\n        count++\n      reset()\n        count: 0\n      default operator ?\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"      register ConsoleLogger() as ConsoleLogger","explanation":"Every injection point (!) must have a matching registration in the application. Registering ConsoleLogger as itself does not provide the abstract Logger the program injects, so the compiler rejects the missing registration with E08210. See ek9 -h E08210 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_add_application","intent":"application","description":"Oracle can generate an application with component registration and program injection wiring."}}
{"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":"#!ek9\ndefines 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      service as Service!\n      stdout <- Stdout()\n      stdout.println(service.greet(\"World\"))\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 Q228 for ordering.","ek9Example":"#!ek9\ndefines 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_add_application","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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_add_application","intent":"aspect","description":"Oracle can add an application that registers a component 'with aspect of' an Aspect; the Aspect subclass itself is created with ek9_new_construct (a class extending Aspect)."}}
{"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":"#!ek9\ndefines 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  try\n    result <- calc.divide(0)\n    stdout.println(`100 / 0 = ${result}`)\n  catch\n    -> ex as Exception\n    stdout.println(`caught: ${ex.reason()}`)\n\nA silent aspect does not catch or modify exceptions -- they pass straight through. In the example the component throws Exception(\"zero divisor\") for a divisor of 0 and the caller's try/catch handles it exactly as if no aspect were present.\n\nSee Q1115 for basic aspect. See Q1116 for aspect with constructor.","ek9Example":"#!ek9\ndefines 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    CheckedCalculator is Calculator\n      override divide()\n        -> divisor as Integer\n        <- result as Integer?\n        if divisor == 0\n          throw Exception(\"zero divisor\")\n        result: 100 / divisor\n\n  defines class\n\n    AuditAspect extends Aspect\n      default operator ?\n\n  defines application\n\n    ExceptionApp\n      register CheckedCalculator() 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}`)\n\n      //An exception thrown by the component passes through the aspect to this try/catch\n      try\n        failed <- calc.divide(0)\n        stdout.println(`100 / 0 = ${failed}`)\n      catch\n        -> ex as Exception\n        stdout.println(`caught: ${ex.reason()}`)","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"E05240","correct":"Shape allow only Circle, Square, Triangle","incorrect":"Shape allow only Circle, Square","explanation":"A sealed trait lists every type that may implement it. Triangle implements Shape but is missing from the 'allow only' list, so the compiler rejects it with E05240. Add the type to the list or do not implement the trait. See ek9 -h E05240 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_generate_dispatcher","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":"#!ek9\ndefines 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":"E05030","correct":"Shape as open","incorrect":"Shape","explanation":"Types are closed by default. A class that other classes extend (Circle, Square and Hexagon extend Shape, which the dispatcher falls back to) must be declared 'as open', otherwise the compiler rejects the extension with E05030. See ek9 -h E05030 for details."}],"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\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\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":"#!ek9\ndefines 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":"E05030","correct":"Quadrilateral is Polygon as open","incorrect":"Quadrilateral is Polygon","explanation":"Every level of the hierarchy that is extended must be 'as open'. Square extends Quadrilateral, so a Quadrilateral that is not open is rejected with E05030. See ek9 -h E05030 for details."}],"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    ->\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\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":"#!ek9\ndefines 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":"E02030","correct":"          s1 as Circle\n          s2 as Rectangle","incorrect":"          s1 as Circle\n          s2 as Circle","explanation":"Each handler is chosen by its parameter types, so two handlers with the same types are duplicate/ambiguous methods and the compiler rejects them with E02030. See ek9 -h E02030 for details."}],"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","Create a dynamic function that captures a greeting and prepends it to any input.","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":"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":"#!ek9\ndefines 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":["abstract","capture","closure","dynamic","extends","function","greeting","lambda","state"],"primaryTopics":["dynamic function","named capture","dynamic function syntax","abstract function"],"typicalErrors":[{"error":"E50001","correct":"      transform <- (prefix: prefixText) extends Transformer as function","incorrect":"      transform <- (prefix: prefixTxt) extends Transformer as function","explanation":"A capture list names an existing local variable: (prefix: prefixText) copies prefixText into a field called prefix. A misspelt variable such as prefixTxt is not resolved. 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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  label <- \"Example\"\n  myObj <- (label) with trait of Describable as class\n    override describe() as pure\n      <- 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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_generate_delegation","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 Maker by delegate\n    delegate as Maker?\n  MiddleProducer with trait of Maker by delegate\n    delegate as Maker?\n  FinalProducer with trait of Maker\n    override produce() as pure <- result as String: \"final\"\n\nCalling top.produce() flows: Top -> Middle -> Final.\n\nA decorator that ends up wrapping itself forwards to itself forever; build decorator chains from distinct objects.\n\nSee Q1129 for basic delegation. See Q1131 for diamond resolution.","ek9Example":"#!ek9\ndefines module qa.sealedtraits.traitdelegationchain\n\n  defines trait\n\n    Maker\n      produce() as pure\n        <- result as String: \"default\"\n\n  defines class\n\n    FinalProducer with trait of Maker\n      override produce() as pure\n        <- result as String: \"final\"\n      default operator ?\n\n    MiddleProducer with trait of Maker by delegate\n      delegate as Maker?\n\n      default private MiddleProducer()\n\n      MiddleProducer()\n        -> prod as Maker\n        require prod?\n        delegate := prod\n\n      default operator ?\n\n    TopProducer with trait of Maker by delegate\n      delegate as Maker?\n\n      default private TopProducer()\n\n      TopProducer()\n        -> prod as Maker\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 Maker by delegate\n      delegate as Maker?","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 and EACH gives a shared method its own body (both override it), the class must override it. A parent method that neither overrides is ONE method reached along both routes - the class has it once, with nothing to resolve. Use TraitName.methodName() to call a specific trait's version.\n\nSee Q1129 for basic delegation. See Q1132 for explicit trait calls.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"E50060","correct":"        <- rtn as String: LeftInfo.getInfo()","incorrect":"        <- rtn as String: LeftInfo.getDetails()","explanation":"TraitName.methodName() must name a method the trait really declares. LeftInfo has getInfo(), not getDetails(), so the call cannot be resolved and the compiler reports E50060. EK9 has no casting, so there is no ((LeftInfo)this) form to reach it. See ek9 -h E50060 for details."}],"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":"'by' delegation forwards EVERY member of the trait that the class does not define itself - methods AND operators, '$' and '?' included:\n\n  PrinterWrapper with trait of Printable by delegate\n    delegate as Printable?\n\nDeclare the operators on the trait, and the wrapper answers them through the delegate: '$wrapper' is the delegate's '$', 'wrapper?' is the delegate's '?', and a binary operator such as '+' is the delegate's '+'. The wrapper needs no 'operator ?' of its own, because the trait declares one.\n\nA member the class declares itself is not forwarded: write 'override operator $' on the wrapper and its own '$' answers, while everything else is still forwarded.\n\nOnly members the TRAIT provides are forwarded: those it declares AND those it inherits from the traits it extends, but never what only Any supplies. If the trait provides no '$', the wrapper's '$' is its own (for example one 'default operator' generates from its fields).\n\nThe delegate is just another provider, so 'by' never resolves a conflict: if the super class, another delegated trait or a trait with a body also provides a member, the class must declare it (E06150 otherwise), and its body may call super or the delegate.\n\nA trait operator that also exists on Any, such as '?', needs 'override' on the trait (E05120 otherwise).\n\nSee Q1129 for basic delegation. See Q1130 for chained delegation.","ek9Example":"#!ek9\ndefines module qa.sealedtraits.traitoperatordelegation\n\n  defines trait\n\n    Printable\n      describe() as pure\n        <- rtn as String: \"default\"\n\n      operator $ as pure\n        <- rtn as String: \"trait-dollar\"\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n      operator + as pure\n        -> amount as Integer\n        <- rtn as String: \"trait-plus\"\n\n  defines class\n\n    RealPrinter with trait of Printable\n      model as String?\n\n      default private RealPrinter()\n\n      RealPrinter()\n        -> initial as String\n        model :=? initial\n\n      override describe() as pure\n        <- rtn as String: \"RealPrinter\"\n\n      override operator $ as pure\n        <- rtn as String: \"real-dollar\"\n\n      //Set only when it has a model\n      override operator ? as pure\n        <- rtn as Boolean: model?\n\n      override operator + as pure\n        -> amount as Integer\n        <- rtn as String: `real-plus-${amount}`\n\n    //Forwards describe(), '$', '?' and '+' to the delegate - no operators of its own\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        delegate := printer\n\n    //Declares its own '$', which wins; everything else is still forwarded\n    LabelledWrapper with trait of Printable by delegate\n      delegate as Printable?\n\n      default private LabelledWrapper()\n\n      LabelledWrapper()\n        -> printer as Printable\n        delegate := printer\n\n      override operator $ as pure\n        <- rtn as String: \"labelled-dollar\"\n\n  defines program\n\n    TraitOperatorDelegationDemo()\n      stdout <- Stdout()\n\n      wrapper <- PrinterWrapper(RealPrinter(\"laser\"))\n\n      //Delegated method call\n      stdout.println(`describe: ${wrapper.describe()}`)\n\n      //Delegated operators: each is answered by RealPrinter\n      stdout.println(`dollar: ${wrapper}`)\n      stdout.println(`isSet: ${wrapper?}`)\n      stdout.println(`no model isSet: ${PrinterWrapper(RealPrinter(String()))?}`)\n      stdout.println(`plus: ${wrapper + 3}`)\n\n      //The wrapper's own '$' wins over the delegate's\n      labelled <- LabelledWrapper(RealPrinter(\"inkjet\"))\n      stdout.println(`labelled dollar: ${labelled}`)\n      stdout.println(`labelled describe: ${labelled.describe()}`)","migrationContext":"Kotlin: class Wrapper(p: Printable): Printable by p - delegates the interface's members, and a member the class writes itself wins. EK9: 'with trait of X by field' - delegates the trait's methods and operators the same way, and a member the class declares itself wins.","keywords":["$","?","by","delegation","forward","operator","trait"],"primaryTopics":["trait operator delegation","operator forwarding"],"typicalErrors":[{"error":"E05120","correct":"      override operator ? as pure\n        <- rtn as Boolean: true","incorrect":"      operator ? as pure\n        <- rtn as Boolean: true","explanation":"'?' already exists on Any, which every trait extends, so declaring it on the trait overrides it and needs 'override'. '$' is not on Any, so it takes no 'override' on the trait."}],"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\n    | filter by isPositive\n    | map by doubleIt\n    | sort\n    > stdout\n\ncat creates the stream, each | adds an operation, > stdout terminates. See Q235, Q1073.","ek9Example":"#!ek9\ndefines 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":"E50060","correct":"cat items\n        | filter by isPositive\n        | map by doubleIt\n        | sort\n        > stdout","incorrect":"items.stream().filter(isPositive).map(doubleIt).sorted()","explanation":"A List has no stream() method, so the call is not resolved (E50060). EK9 uses 'cat source | operation | operation > output' syntax, not method chaining. See ek9 -h E50060 for details."}],"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":"To sort by price, give the stream a comparator function and use 'sort by':\n  sorted <- cat items | sort by comparePrice | collect as List of Item\n\nA bare 'sort' uses the class's 'default operator', which generates <=> comparing fields in declaration order. For Item, name is declared first, so a bare sort orders by name, not price. Use 'sort by' with a comparator (or declare price first) to order by price. See Q1085, Q1073.","ek9Example":"#!ek9\ndefines module qa.streams.streamrecordpipeline\n\n  defines record\n\n    Item\n      name as String: String()\n      price as Float: 0.0\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 function\n\n    comparePrice() as pure\n      ->\n        left as Item\n        right as Item\n      <- rtn as Integer: left.price <=> right.price\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 price with a comparator (a bare sort would use the default <=> and order by name)\n      sorted <- cat items | sort by comparePrice | 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 by comparePrice | collect as List of Item","incorrect":"      items.sort(Comparator.comparing(Item::getPrice))","explanation":"EK9 uses stream pipelines for sorting: write a comparator function and use cat | sort by comparator."}],"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":"uniq keeps the first occurrence of each value and drops every later one that is == to it, adjacent or not, so it alone deduplicates:\n  cat items | uniq > stdout\n\nAdd sort to get the unique values in order:\n  cat items | sort | uniq > stdout\n\nThe sort is for the ORDER of the result, not for uniq (which is not consecutive-only, unlike Unix uniq or Rust dedup). See Q989, Q891.","ek9Example":"#!ek9\ndefines 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      //uniq deduplicates on its own (global first-seen); the sort orders the result\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() - EK9 uniq is like distinct(), global first-seen. Python: sorted(set(items)). Rust: dedup() only drops consecutive repeats, so it needs a sort first; EK9 uniq does not. EK9: sort | uniq gives sorted unique values.","keywords":["deduplicate","distinct","flatten","group","sort","uniq","unique"],"primaryTopics":["uniq deduplication","sort uniq pipeline"],"typicalErrors":[{"error":"E50060","correct":"      cat items | sort | uniq > stdout","incorrect":"      items.distinct()","explanation":"A List has no distinct() method, so the call is not resolved (E50060). EK9 deduplicates in a stream with 'uniq' (cat items | uniq). It keeps the first of each equal item anywhere in the stream; the sort only orders the result. See ek9 -h E50060 for details."}],"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":"#!ek9\ndefines 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":"      items.limit(3)","explanation":"A List has no limit() method, so the call is not resolved (E50060). EK9 uses 'head N' in a stream pipeline, not limit(). Similarly 'tail N' and 'skip N'. See ek9 -h E50060 for details."}],"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":"#!ek9\ndefines 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":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":"#!ek9\ndefines 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":"upper <- fruits.stream()","explanation":"EK9 List has no stream() method; it uses a cat pipeline with named functions ('filter by' and 'map by'), not Java-style lambdas. See ek9 -h E50060 for details."}],"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":"#!ek9\ndefines 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      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()","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":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  //The same pattern works in switch and while (a loop guard is evaluated once on entry; 'with' is re-tested each iteration):\n  while conn <- getConnection() with counter < maxIters\n    process(conn)\n    counter: counter + 1\n  switch kind <- database.kindOf(id) with kind\n    case == \"USER\"\n      handleUser(kind)\n    default\n      handleOther(kind)\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 :=? assign-if-unset.","ek9Example":"#!ek9\ndefines 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"],"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":"#!ek9\ndefines 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":[],"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 on a descending range use a negative step: 'for i in 10 ... 1 by -2' visits 10, 8, 6, 4, 2. A positive step on a descending range ('10 ... 1 by 2') runs no iterations. See Q1149.","ek9Example":"#!ek9\ndefines 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'","How do I count down in steps with 'by' in an EK9 for-range?"],"answer":"'by' specifies the step value:\n  for i in 0 ... 20 by 5\n    stdout.println($i)\n\nWith 'by', the SIGN of the step sets the direction. To count down, use a negative step:\n  for i in 20 ... 0 by -5\n    stdout.println($i)\nA positive step on a descending range (for i in 20 ... 0 by 5) runs zero times. Only when 'by' is left out does EK9 work out the direction from start vs end (for i in 20 ... 0 counts down by 1). See Q1148.","ek9Example":"#!ek9\ndefines 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)\n\n      //Descending: with 'by', the sign of the step sets the direction\n      stdout.println(\"By -5:\")\n      for i in 20 ... 0 by -5\n        stdout.println($i)\n\n      //A positive step on a descending range runs zero times\n      stdout.println(\"20 ... 0 by 5:\")\n      for i in 20 ... 0 by 5\n        stdout.println($i)\n      stdout.println(\"(no iterations)\")","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 (count down with a negative step: for i in 20 ... 0 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","Does a while guard call its function again on every iteration?"],"answer":"Fetch the first value before the loop, test it in the while condition, and fetch the next one at the end of the body:\n  current <- source.getConnection()\n  while current?\n    stdout.println(current)\n    current: source.getConnection()\n\nThe loop ends cleanly when getConnection() returns an unset value.\n\nA while GUARD does not do this. In 'while conn <- supplier.getConnection() with counter < maxIters' the guard runs ONCE, before the loop: it takes one connection (and skips the loop entirely if that is unset), and only the 'with' condition is re-checked on each iteration. So conn stays the first connection for every pass. Use a guard to set up the loop, not to poll. See Q77 for while guard details. See Q971 for declaration guards.","ek9Example":"#!ek9\ndefines 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      source <- ConnectionSupplier(4)\n      processed <- 0\n\n      // Loop while getConnection() returns a SET value: fetch first, re-fetch at the end of the body\n      current <- source.getConnection()\n      while current?\n        stdout.println(current)\n        processed: processed + 1\n        current: source.getConnection()\n\n      stdout.println(`Processed ${processed} connections`)\n\n      // A while GUARD runs ONCE, before the loop: conn is the first connection on every pass,\n      // and only the 'with' condition is re-checked\n      supplier <- ConnectionSupplier(2)\n      counter <- 0\n      maxIters <- 3\n      while conn <- supplier.getConnection() with counter < maxIters\n        stdout.println(`Guarded: ${conn}`)\n        counter: counter + 1","migrationContext":"Java: while ((val = getNext()) != null) { process(val); }. Go: for val := getNext(); val != nil; val = getNext(). Rust: while let Some(v) = get_next(). EK9: val <- getNext(), then while val? with val: getNext() at the end of the body. A while guard (while val <- getNext() with cond) runs once, before the loop, so it does not poll.","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 guards 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":"#!ek9\ndefines 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":[],"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 assign-if-unset 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","Apply a default port number only when the config port is unset.","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":"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.\n\nZERO IS A VALUE\n':=?' assigns only when the variable is unset. A port of 0 is set, so it is kept - unlike 'port or 8080' in Python or 'if port == 0' in Go, which replace a valid 0.\n\nSee Q1038, Q1091.","ek9Example":"#!ek9\ndefines 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","config","default","fallback","guarded","integer","port","server"],"primaryTopics":[":=? assign-if-unset","conditional default","integer defaults","server configuration"],"typicalErrors":[{"error":"E01073","correct":"      port :=? 8080","incorrect":"      if port == null\n        port = 8080","explanation":"EK9 has no null. Use :=? for assign-if-unset — 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":"#!ek9\ndefines 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":"        label <- \"Unknown\"\n        if score > excellentThreshold\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":"#!ek9\ndefines 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(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":[],"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":"#!ek9\ndefines 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":[],"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":"#!ek9\ndefines 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(e.reason())\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":[],"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","Does finally still run when the try guard 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 WHOLE try statement is skipped: the body, catch AND finally. The guard gates the statement as it gates an if. Once the guard is set and the try is entered, finally always runs. Put cleanup that must run whatever the guard gives outside the guarded try.\n\nSee Q1146 for if-guard. See Q1154 for basic try/catch.","ek9Example":"#!ek9\ndefines module qa.controlflow.tryguardexpression\n\n  defines function\n\n    lookup()\n      -> available as Boolean\n      <- rtn as Integer: Integer()\n      if available\n        rtn: 42\n\n  defines program\n\n    TryGuardExpressionDemo()\n      stdout <- Stdout()\n\n      //Set guard — try body executes\n      setValue <- lookup(true)\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: runs once the try is entered\")\n\n      //Unset guard — the whole try is skipped, finally included\n      unsetValue <- lookup(false)\n      try skipped <- unsetValue\n        stdout.println(`Body: never printed ${skipped}`)\n      catch\n        -> e as Exception\n        stdout.println(\"Catch: never printed\")\n      finally\n        stdout.println(\"Finally: never printed\")\n      stdout.println(\"After the unset guard: whole try skipped\")","migrationContext":"Swift: guard let value = optional else { return }. Kotlin: value?.let { }. EK9: try value <- expr — guard on the try itself; an unset guard skips the whole try, finally included.","keywords":["conditional","guard","set","skip","try","unset"],"primaryTopics":["try guard","conditional try execution"],"typicalErrors":[{"error":"E01073","correct":"setValue <- lookup(true)","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":"#!ek9\ndefines 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":"E06020","correct":"      ages <- Dict() of (String, Integer)","incorrect":"      ages <- Dict() of String","explanation":"Dict needs two type parameters, key and value, in parentheses: Dict() of (K, V). See ek9 -h E06020 for details."}],"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":"#!ek9\ndefines 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  stdout.println(names.getOrDefault(0, \"none\"))\n  names -= \"Bob\"\n  cat names > stdout\n\n+= adds items and -= removes one. names.getOrDefault(0, \"none\") accesses an item by position (List has no get(i)). 'length' prefix operator returns size. 'cat list > stdout' prints all items.\n\nSee Q1158 for Dict iteration. See Q1081 for list merge.","ek9Example":"#!ek9\ndefines 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}`)\n\n      //Access by position, with a default if the position does not exist\n      first <- names.getOrDefault(0, \"none\")\n      stdout.println(`First: ${first}`)\n\n      //Remove an item\n      names -= \"Bob\"\n      stdout.println(`After remove: ${names}`)","migrationContext":"Java: list.add(), list.size(), list.get(). Python: list.append(), len(list). EK9: += to add, -= to remove, getOrDefault(index, default) to access, 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":"#!ek9\ndefines 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":"E06020","correct":"      codes <- Dict() of (String, String)","incorrect":"      codes <- Dict() of String","explanation":"Dict needs two type parameters, key and value, in parentheses: Dict() of (K, V). See ek9 -h E06020 for details."}],"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":"#!ek9\ndefines 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":"E50060","correct":"positives <- cat numbers | filter by isPositive | collect as List of Integer","incorrect":"numbers.removeIf(isPositive)","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 Q1035 for collect as.","ek9Example":"#!ek9\ndefines 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. Double quotes never interpolate: \"Name: ${name}\" is a compile error (E07901). See Q909, Q918, Q43.","ek9Example":"#!ek9\ndefines 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. Kotlin, Groovy, Dart, PHP and the shells interpolate DOUBLE quotes; carried into EK9, \"Hello ${name}\" is a compile error (E07901).","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."},{"error":"E07901","correct":"stdout.println(`Name: ${name}, Age: ${age}`)","incorrect":"stdout.println(\"Name: ${name}, Age: ${age}\")","explanation":"Only a backtick String interpolates; a double-quoted String holding ${ is a compile error (E07901) instead of printing the characters. See ek9 -h E07901 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":"#!ek9\ndefines 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":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, Q1088.","ek9Example":"#!ek9\ndefines 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: the guard sets the variable and runs the block. Not found: the return is unset, so the else runs\n      for colourName in [\"sky\", \"ocean\"]\n        if found <- findColour(colourName)\n          stdout.println(`Found ${colourName}: ${found}`)\n        else\n          stdout.println(`No colour found for ${colourName}`)","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":"A return variable must be initialised (or declared with ?). The return here has no initialiser. Optional is a valid type, but the simpler EK9 idiom is to initialise with an unset String() and let the caller guard with ?. See ek9 -h E08180 for details."}],"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?\n    maxAge <- 150\n    rtn: age >= 0 and age <= maxAge\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"E50001","correct":"      case Priority.HIGH","incorrect":"      case HIGH","explanation":"EK9 enum values must be qualified with the type name in a case: Priority.HIGH, not just HIGH, which does not resolve. See ek9 -h E50001 for details."}],"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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 one day. DateTime ++ and -- also move by one day (the time of day is kept). See Q1103.","ek9Example":"#!ek9\ndefines 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":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":"#!ek9\ndefines 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 E50060 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":"#!ek9\ndefines 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_adopt_trait","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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      gadgetName <- \"Gadget\"\n      prices += DictEntry(\"Widget\", 9.99)\n      prices += DictEntry(gadgetName, 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 with contains; getOrDefault only supplies a fallback\n      if prices contains gadgetName\n        found <- prices.getOrDefault(gadgetName, 0.0)\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":"Create a Dict with a curly-brace {key: value} literal, then add entries using DictEntry.","url":"https://ek9.io/qa/QA1179.html","alternatePhrasings":["Write code showing how to create an EK9 dict with a {key: value} literal","How do I 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 with a literal and then add more entries with DictEntry"],"answer":"EK9 has a dict literal: curly braces around key: value pairs. The key and value types are inferred:\n\n  settings <- {\"theme\": \"dark\", \"lang\": \"en\"}    // Dict of (String, String)\n\nTo add entries later, use DictEntry(key, value) with +=:\n\n  settings += DictEntry(\"fontSize\", \"14\")\n\nThe entry must match the Dict's types: DictEntry(\"fontSize\", 14) is a DictEntry of (String, Integer), so adding it to a Dict of (String, String) is E50060 (method not resolved).\n\nTo start with an empty Dict, give the types explicitly: settings <- Dict() of (String, String).\n\nSee Q1178 for Dict creation. See Q1161 for full Dict operations.","ek9Example":"#!ek9\ndefines module qa.collections.dictnoliteralsyntax\n\n  defines program\n\n    DictNoLiteralSyntaxDemo()\n      stdout <- Stdout()\n\n      //A dict literal: the types are inferred as Dict of (String, String)\n      settings <- {\"theme\": \"dark\", \"lang\": \"en\"}\n\n      //Add more entries with DictEntry\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: {k: v, k2: v2} literal (types inferred, e.g. Dict of (String, String)); add entries later with += DictEntry(k, v), or start empty with Dict() of (K, V).","keywords":["Dict","DictEntry","curly brace","dict literal","literal","syntax"],"primaryTopics":["dict literal","DictEntry syntax"],"typicalErrors":[{"error":"E50060","correct":"      settings += DictEntry(\"fontSize\", \"14\")","incorrect":"      settings += DictEntry(\"fontSize\", 14)","explanation":"The literal made settings a Dict of (String, String), so every DictEntry added must be (String, String). DictEntry(\"fontSize\", 14) is (String, Integer) and no += accepts it. See ek9 -h E50060 for details."}],"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","Can an abstract class declare operator $ as abstract when it has fields?"],"answer":"Abstract classes use 'as abstract' and concrete children override:\n\n  Shape as abstract\n    name as String: String()\n    area() as abstract\n      <- rtn as Float?\n    default operator ?\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\nWHERE 'default operator' CAN GO\nShape takes 'default operator ?' and 'default operator $', so Circle can default '$' too: its generated '$' calls Shape's and adds its own field - 'Circle(Shape(name=circle), radius=5.0)'. Circle must NOT add a bare 'default operator': that asks for a generated <=> built on the super's <=>, and Shape has none, so it is E07190 (requires operator '<=>(Shape)'). Circle defines just the operators it needs (? and $).\nAn abstract class may itself take a full 'default operator', and a child's 'default operator' then compiles. But sameness (== and <=>) is defined once per hierarchy, at Shape, so the child's 'default operator' does not give Circle its own == or <=> - and two Circles with different radii compare EQUAL under Shape's sameness (Circle(2.0) == Circle(3.0) is true). That is a poor fit for a Shape hierarchy, which is why this example does not use it.\n\nAN ABSTRACT '$' ONLY WITHOUT FIELDS\nShape has a field ('name'), so its '$' cannot be abstract: 'operator $ as pure abstract' here is E07185. Below an abstract '$' a sub-type's generated '$' covers only the sub-type's own fields, so Shape's 'name' would silently be left out of every Circle's string. Give the operator a body or 'default operator $' (a hand-written body when the fields are not to be shown). The same holds for '$$' and '#?'. A field-less root, or a trait, may keep '$' abstract - there is nothing to lose.\n\nSee Q103 for abstract classes. See Q116 for default operator.","ek9Example":"#!ek9\ndefines module qa.classesandoop.abstractclassoperators\n\n  defines class\n\n    Shape as abstract\n      name as String: String()\n\n      Shape()\n        -> shapeName as String\n        name :=? shapeName\n\n      area() as abstract\n        <- rtn as Float?\n\n      default operator ?\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      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      default operator $\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      stdout.println(`Circle: ${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","abstract operator","area","circle","class","default operator","override","shape"],"primaryTopics":["abstract class","concrete subclass"],"typicalErrors":[{"error":"E07130","correct":"      override area()\n        <- rtn as Float: Float()\n        pi <- 3.14159\n        rtn: radius * radius * pi","incorrect":"      //missing override of area()","explanation":"Concrete subclasses must implement all abstract methods. Omitting the override triggers E07130."},{"error":"E07185","correct":"      default operator ?\n      default operator $\n\n    Circle extends Shape","incorrect":"      default operator ?\n      operator $ as pure abstract\n        <- rtn as String?\n\n    Circle extends Shape","explanation":"Shape has a field ('name'), so its '$' cannot be abstract: a sub-type's generated '$' below an abstract one covers only the sub-type's own fields, and every Circle's string would silently leave 'name' out. Use 'default operator $' (or give '$' a body) on the type that has the fields; only a field-less root or a trait may keep '$' abstract. See ek9 -h E07185 for details."}],"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      ->\n        first as A\n        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":"#!ek9\ndefines 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":"      //Constructor 2: parameterised (used by code)\n      Pair()","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!), and injecting the concrete type is rejected with E08150. The compiler validates a matching registration exists.\n\nSee Q1110 for DI basics. See Q227 for compile-time validation. See Q228 for ordering.","ek9Example":"#!ek9\ndefines 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":"E08150","correct":"logger as Logger!","incorrect":"logger as ConsoleLogger!","explanation":"Injection fields must use the abstract type (Logger!), not the concrete type (ConsoleLogger!). The compiler rejects an injection of a non-abstract component with E08150. See ek9 -h E08150 for details."}],"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":"#!ek9\ndefines 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":"      rtn: 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 any value the compiler can PROVE breaches the constraint is the compile error E08260 — moving it into a variable or a constant first does not defer the check. 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":"#!ek9\ndefines 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; so would Percentage(limit) after 'limit <- 150', and BOTH are the compile error\n      //E08260 because the value is statically known either way)\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":"E08260","correct":"passing <- Percentage(85)","incorrect":"passing <- Percentage(185)","explanation":"A bare constructor ASSERTS validity, so a value the compiler can prove is out of range is rejected now rather than Panicking later. Assigning the literal to a variable or a module constant first does NOT defer the check — flow analysis follows the value that ARRIVES at the constructor, through reassignment, branches, ternary and switch arms, guards and captures. Use the fallible factory .of() when the value genuinely is not known until runtime; it returns an UNSET object instead of Panicking. See ek9 -h E08260 for details."},{"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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 supports ++, --, comparison, <=>, $ and ?, but not arithmetic with Integer (letter + 1 is rejected).\n\nSee Q238 for the complete operator set. See Q23 for basic types.","ek9Example":"#!ek9\ndefines 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++\n      stdout.println(`After first ++: ${letter}`)","incorrect":"letter = letter + 1\n      stdout.println(`After first ++: ${letter}`)","explanation":"Character does not support addition with Integer. Use the ++ operator to increment a Character to the next Unicode code point. See ek9 -h E07620 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"E01086","correct":"      default operator\n\n  defines program","incorrect":"      default operator\n      extra as String: String()\n\n  defines program","explanation":"The 'default operator' must be the LAST declaration in the record body. Placing fields or methods after it triggers E01086. See ek9 -h E01086 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":"#!ek9\ndefines 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?\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":"#!ek9\ndefines 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 E50060 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 is the class\n  - startDate <- Date() and gap <- Duration() are its two fields\n  - startDate() and gap() are accessor methods for those fields\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":"#!ek9\ndefines 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 sprintProject.endDate() < hotfix.endDate()\n        stdout.println(\"Sprint project finishes before hotfix\")\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\n    Project\n      startDate <- Date()","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":"#!ek9\ndefines 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 E50001 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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)\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)\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":"E01073","correct":"      if deadline <- findDeadline()","incorrect":"      if deadline != null","explanation":"EK9 has no null and no isBefore() method. Use guard variables in if statements and comparison operators on Date. See ek9 -h E01073 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 Q539 for duration between dates. See Q1188 for duration operations. See Q1186 for date comparison.","ek9Example":"#!ek9\ndefines 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 by | 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\nCOMPARATOR FUNCTION\n  byPrice() as pure\n    ->\n      left as Product\n      right as Product\n    <- rtn as Integer: left.price <=> right.price\n\nPIPELINE\n  expensive <- cat products | filter by isExpensive | sort by byPrice | collect as List of Product\n\nThe filter stage applies the predicate. 'sort by byPrice' orders by price ascending (Webcam 65.00, Headset 79.50, Monitor 299.99). A bare '| sort' would use the record's default <=>, which compares the properties in declaration order - productName first - so it would sort by NAME, not price. 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":"#!ek9\ndefines 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    byPrice() 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    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 BY PRICE ===\n\n      // Prints Webcam (65.00), Headset (79.50), Monitor (299.99)\n      expensive <- cat products | filter by isExpensive | sort by byPrice | collect as List of Product\n      stdout.println(\"Expensive products (sorted):\")\n      cat expensive > stdout\n\n      // === DIRECT OUTPUT ===\n\n      // Same order, written straight to stdout\n      stdout.println(\"Expensive to stdout:\")\n      cat products | filter by isExpensive | sort by byPrice > 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 by byPrice | collect — pipe syntax with a named predicate and a named comparator.","keywords":["comparator","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 by byPrice | collect as List of Product","incorrect":"expensive <- products.stream()","explanation":"EK9 List has no .stream() method; stream it with 'cat products' and pipe it through named functions. 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":"#!ek9\ndefines 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(theHireDate: 2024-06-15, theName: \"Alice\", theDepartment: \"Engineering\")\n      employees += Employee(theHireDate: 2023-01-10, theName: \"Bob\", theDepartment: \"Marketing\")\n      employees += Employee(theHireDate: 2024-01-05, theName: \"Carol\", theDepartment: \"Engineering\")\n      employees += Employee(theHireDate: 2022-09-20, theName: \"Dave\", theDepartment: \"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(theHireDate: 2024-06-15, theName: \"Alice\", theDepartment: \"Engineering\")\n      bob <- Employee(theHireDate: 2023-01-10, theName: \"Bob\", theDepartment: \"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":"E01086","correct":"      default operator\n\n  defines program","incorrect":"      default operator\n      note as String: String()\n\n  defines program","explanation":"The 'default operator' must be the LAST declaration in the record body. Placing a field or method after it (here 'note') triggers E01086. See ek9 -h E01086 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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      // === COMPARISON COALESCING ===\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":"#!ek9\ndefines 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    ->\n      left as Date\n      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 :=? (assign-if-unset). 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":"#!ek9\ndefines 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    ->\n      left as Money\n      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 :=? (assign-if-unset), 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":"#!ek9\ndefines 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    ->\n      left as Duration\n      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 :=? (assign-if-unset), 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":"#!ek9\ndefines 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    ->\n      left as Time\n      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 :=? (assign-if-unset), 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":"#!ek9\ndefines 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    ->\n      left as Integer\n      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 :=? (assign-if-unset), 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":"#!ek9\ndefines 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    ->\n      left as Float\n      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 :=? (assign-if-unset), 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":"#!ek9\ndefines 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    ->\n      left as Money\n      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 :=? (assign-if-unset), 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":"#!ek9\ndefines 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    ->\n      left as Date\n      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 :=? (assign-if-unset), 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":"#!ek9\ndefines 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    ->\n      left as Duration\n      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 :=? (assign-if-unset), 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":"#!ek9\ndefines 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    ->\n      left as Integer\n      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 :=? (assign-if-unset), 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":"#!ek9\ndefines 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    ->\n      left as Time\n      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 compare as equal: <? returns the RIGHT operand and <=? returns the LEFT one (likewise >? returns the right and >=? the left). For Time values equal means identical, so you cannot tell; it matters when <=> ties on objects that differ, such as a record whose <=> compares one field. The full coalescing family is <?, >?, <=?, >=?. See Q1227 for all four operators side by side.\n\nDo not confuse any coalescing operator with :=? (assign-if-unset). See Q1226 for the contrast.","ek9Example":"#!ek9\ndefines 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    ->\n      left as Float\n      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 :=? (assign-if-unset). See Q1226 for the contrast.","ek9Example":"#!ek9\ndefines 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 :=? assign-if-unset 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":"#!ek9\ndefines 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    lookupKnownName()\n      <- rtn <- \"Alice\"\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 <- lookupKnownName()\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":[":=? assign-if-unset","string defaults","conditional initialization"],"typicalErrors":[{"error":"E01073","correct":"      knownName <- lookupKnownName()\n      knownName :=? \"Guest\"","incorrect":"      knownName <- lookupKnownName()\n      if knownName == null\n        knownName := \"Guest\"","explanation":"EK9 has no null — writing '== null' references an unsupported literal; use the assign-if-unset ':=?' to set a value only when the variable is currently unset. See ek9 -h E01073 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 :=? assign-if-unset 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":"#!ek9\ndefines 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":[":=? assign-if-unset","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 E01073 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 :=? assign-if-unset 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":"#!ek9\ndefines 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":[":=? assign-if-unset","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 E01073 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 :=? assign-if-unset 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":"#!ek9\ndefines 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":[":=? assign-if-unset","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 E01073 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 :=? assign-if-unset 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":"#!ek9\ndefines 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":[":=? assign-if-unset","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 E01073 for details."}],"companions":[]}
{"id":1226,"category":"Operators and Expressions","question":"Demonstrate the difference between <? comparison coalescing and :=? assign-if-unset.","url":"https://ek9.io/qa/QA1226.html","alternatePhrasings":["What is the distinction between <? and :=? in EK9?","I keep confusing <? coalescing with :=? assign-if-unset — 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    ->\n      left as Money\n      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":"#!ek9\ndefines 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: :=? ASSIGN-IF-UNSET — 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 assign-if-unset. Each handles unset values automatically.","keywords":[":=?","<?","assignment","coalescing","comparison","comparison coalescing","contrast","distinction"],"primaryTopics":["<? vs :=? distinction","comparison coalescing","assign-if-unset"],"typicalErrors":[],"companions":[]}
{"id":1227,"category":"Operators and Expressions","question":"Show all four comparison coalescing 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 comparison coalescing operators with Integer examples."],"answer":"EK9 has four comparison coalescing operators. All handle unset values gracefully.\n\n  minOf() as pure\n    ->\n      left as Integer\n      right as Integer\n    <- rtn as Integer: left <? right     //lesser\n  maxOf() as pure\n    ->\n      left as Integer\n      right as Integer\n    <- rtn as Integer: left >? right     //greater\n  minOrEqual() as pure\n    ->\n      left as Integer\n      right as Integer\n    <- rtn as Integer: left <=? right    //lesser-or-equal\n  maxOrEqual() as pure\n    ->\n      left as Integer\n      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 :=? (assign-if-unset), which is a completely different operation.\n\nSee Q1226 for the distinction between <? and :=?. See Q1092 for min/max coalescing basics. ","ek9Example":"#!ek9\ndefines 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, so declare price first for a cheaper-first ordering). Then use <? in a pure function to pick the lesser product.\n\n  cheaperProduct() as pure\n    ->\n      left as Product\n      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":"#!ek9\ndefines module qa.operators.coalescemin.record\n\n  defines record\n\n    Product\n      price as Float: 0.0\n      name as String: String()\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":"A record with properties must define the '?' operator (set/unset semantics); without it you get E07235. 'default operator' supplies '?' and also the <=> that <? needs - without a <=>, each use of <? is E07620. See ek9 -h E07235 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, with a hand-written #? hashing the same field (a hand-written <=> needs one - E07184). Lower priority number means higher priority, so use <? to pick the more urgent task.\n\n  higherPriority() as pure\n    ->\n      left as Task\n      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":"#!ek9\ndefines 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      //A hand-written <=> needs a hand-written #? hashing the same field\n      operator #? as pure\n        <- rtn as Integer: #?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  operator #? as pure\n    <- rtn as Integer: #?reading\n\nA hand-written <=> needs a hand-written #? hashing the same field (E07184).\n\n  smallerReading() as pure\n    ->\n      left as Measurement\n      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":"#!ek9\ndefines 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      //A hand-written <=> needs a hand-written #? hashing the same field\n      operator #? as pure\n        <- rtn as Integer: #?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 with a matching hand-written #? (E07184 without it), and 'default operator' last. Then use <? in a pure function.\n\n  nearerWaypoint() as pure\n    ->\n      left as Waypoint\n      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":"#!ek9\ndefines 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      //A hand-written <=> needs a hand-written #? hashing the same field\n      operator #? as pure\n        <- rtn as Integer: #?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      stdout.println(`Base: ${basecamp.name()}`)\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":"E06180","correct":"      stdout.println(`Base: ${basecamp.name()}`)","incorrect":"      stdout.println(`Base: ${basecamp.waypointName}`)","explanation":"Class properties are private to the class. To expose state, define accessor methods and call them. See ek9 -h E06180 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 :=? assign-if-unset 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 :=? assign-if-unset 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":"#!ek9\ndefines 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","assign-if-unset 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 E01073 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    ->\n      left as SensorReading\n      right as SensorReading\n    <- rtn as SensorReading: left <? right\n\n  coolestList <- cat readings | join with coolerOf | collect as List of SensorReading\n\nThe <? operator handles unset values gracefully within the stream. The custom <=> on temperature is the record's sameness: it determines ordering, and ==, <>, <, <=, > and >= are generated from it. Its hand-written '<=>' needs a hand-written '#?' hashing the same field.\n\nSee Q1139 for join basics. See Q1230 for custom <=> on records.","ek9Example":"#!ek9\ndefines 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      operator #? as pure\n        <- rtn as Integer: #? 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      -> item 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      -> item as String\n      <- rtn as String: \"Processed: \" + item\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":"#!ek9\ndefines module qa.genericsdeep.processorbasic\n\n  defines class\n\n    Processor of type T as abstract\n\n      process() as abstract\n        -> item 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          -> item as String\n          <- rtn as String: \"Processed: \" + item\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      -> item 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      -> item as Integer\n      <- rtn as String: `${prefix}: ${$item}`\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":"#!ek9\ndefines module qa.genericsdeep.formattercapture\n\n  defines class\n\n    Formatter of type T as abstract\n\n      format() as abstract\n        -> item 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          -> item as Integer\n          <- rtn as String: `${prefix}: ${$item}`\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\n        override format()\n          -> item as Integer\n          <- rtn as String: `${prefix}: ${$item}`","incorrect":"() extends Formatter of Integer as class\n        override format()\n          -> item as Integer\n          <- rtn as String: `${prefix}: ${$item}`","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":"#!ek9\ndefines 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":"E05170","correct":"describe() as dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Unknown\"","incorrect":"describe() as pure dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Unknown\"","explanation":"The dispatcher entry and all handlers must agree on purity. If the entry is declared as pure dispatcher, every handler must also be declared as pure. Here the handlers are not pure, so the compiler reports a purity mismatch. See ek9 -h E05170 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 receiving function runs the sanitizer on entry, at run time, so its body only sees safe 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 run-time cleaning on entry makes the parameter 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":"#!ek9\ndefines 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":"E07910","correct":"-> input as sanitized String","incorrect":"-> input as sanitized Integer","explanation":"The 'sanitized' modifier can only be applied to String parameters, so 'input as sanitized Integer' is rejected with E07910. See ek9 -h E07910 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  defines function\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":"#!ek9\ndefines 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 the first two outright; the null check becomes a check for an empty List (see USAGE).\n\nTHE EK9 STREAM EQUIVALENT\nBIG_THRESHOLD is a named constant (BIG_THRESHOLD <- 50 under defines constant), because EK9 refuses a bare literal in a comparison (E11064). Use 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 > BIG_THRESHOLD\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 is not empty\n    stdout.println(`First > 50: ${firstBig}`)\nA collected List is always SET, even when empty, so 'firstBig?' can never signal 'no match'; test for emptiness instead.\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 assign-if-unset. See Q288 for migrating break loops. See Q142 for stream pipeline basics.","ek9Example":"#!ek9\ndefines 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 is not empty\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 >= HIGH_THRESHOLD\n      rtn: \"high\"\n    else if score >= MEDIUM_THRESHOLD\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. (HIGH_THRESHOLD and MEDIUM_THRESHOLD are constants, 80 and 50 here, because comparing against a bare literal is rejected as a magic literal, E11064.)\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 >= HIGH_THRESHOLD\n      rtn: \"high\"\n    else if score >= MEDIUM_THRESHOLD\n      rtn: \"medium\"\n    else\n      rtn: \"low\"\n\nThe final 'else' is required — without it, a score below MEDIUM_THRESHOLD would leave 'rtn' uninitialised and the compiler raises E08050.\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":"#!ek9\ndefines 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":["E08050","branch","data flow","default value","if else","initialisation","return variable","uninitialised"],"primaryTopics":["return variable initialisation","branch safe assignment","default declaration"],"typicalErrors":[{"error":"E08050","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 >= HIGH_THRESHOLD\n        rtn: \"high\"\n      else if score >= MEDIUM_THRESHOLD\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 E08050 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 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 declaration guards 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 declaration guard 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. A function taking EmailAddress receives either a valid value or an unset one, never an invalid set value. Checking with a guard, as above, is the recommended pattern, though the compiler does not force the check.\n3. No exceptions for predictable input failures — unset is the natural representation.\n\nSee Q269 for input validation patterns. See Q257 for constrained type basics. See Q74 for declaration guards.","ek9Example":"#!ek9\ndefines 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 guards 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 UnknownBase constrain as\n      matches /^.+@.+$/","explanation":"The base type of a constrained type must be a type the compiler can resolve. 'UnknownBase' is not declared anywhere, so E50010 is reported on that line. (A user-defined record or class is a valid base only when it is copyable and provides the operator the constraint uses.) 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      -> candidate as T\n      <- rtn as Boolean?\n    describe() as abstract\n      -> candidate 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      -> candidate as Date\n      <- rtn as Boolean: candidate >= EARLIEST_VALID_DATE and candidate <= LATEST_VALID_DATE\n    override describe()\n      -> candidate as Date\n      <- rtn as String: \"Date \" + $candidate\n\nEach overridden method independently uses the type-substituted parameter (candidate 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":"#!ek9\ndefines 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        -> candidate as T\n        <- rtn as Boolean?\n\n      describe() as abstract\n        -> candidate 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          -> candidate as Date\n          <- rtn as Boolean: candidate >= EARLIEST_VALID_DATE and candidate <= LATEST_VALID_DATE\n        override describe()\n          -> candidate as Date\n          <- rtn as String: \"Date \" + $candidate\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          -> candidate as Date\n          <- rtn as Boolean: candidate >= EARLIEST_VALID_DATE and candidate <= LATEST_VALID_DATE\n        override describe()\n          -> candidate as Date\n          <- rtn as String: \"Date \" + $candidate","incorrect":"override check()\n          -> candidate as Date\n          <- rtn as Boolean: candidate >= EARLIEST_VALID_DATE and candidate <= LATEST_VALID_DATE","explanation":"Both abstract methods (check and describe) must be overridden in the dynamic class. Leaving describe unimplemented means the dynamic class declaration is rejected, because every abstract method must be implemented. 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; supplying only one type triggers E06020 (generic, but incorrect number of parameters supplied).\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":"#!ek9\ndefines 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":"E06020","correct":"transformer <- () extends Transformer of (String, Integer) as class","incorrect":"transformer <- () extends Transformer of String as class","explanation":"Transformer declares two type parameters (K, V), so the extension site must supply two types in parentheses: 'Transformer of (String, Integer)'. Supplying only one triggers E06020 because the number of type parameters does not match. See ek9 -h E06020 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 path traversal, terminal escape sequences) 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 payload types — file path traversal, ANSI terminal escape codes (which the runtime reports as LOG_INJECTION), and a bare embedded newline — rather than the usual SQL/XSS strings, to show that 'sanitized' applies to ANY untrusted input, not just web payloads. It also shows what is NOT rejected: a plain newline inside the text is not treated as a threat, so the logger reports 'clean' for it:\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 (reported as LOG_INJECTION)\n  log3 <- AuditLogger(\"Login\\u001b[31m FAILED \\u001b[0m\")\n  // \"rejected\"\n\n  // A bare embedded newline is NOT flagged\n  log4 <- AuditLogger(\"action=lookup\n2025-01-01 ADMIN_OVERRIDE\")\n  // \"clean\"\n\nEach rejection also writes a JSON warning line to stderr naming the threat (PATH_TRAVERSAL, LOG_INJECTION). If your log format is line-oriented, split or escape newlines yourself: 'sanitized' does not do it for you.\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":"#!ek9\ndefines 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":"E07910","correct":"-> incomingMessage as sanitized String","incorrect":"-> incomingMessage as sanitized Integer","explanation":"The sanitized modifier can only be applied to String parameters. Injection attacks travel as text, so a non-String type has nothing to clean. See ek9 -h E07910 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 (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 show that 'sanitized' protects against any untrusted text. It also shows that a URL-shaped fragment is not, by itself, a threat: an ordinary redirect parameter is accepted:\n  qb <- QueryBuilder()\n\n  // Clean equality clause\n  qb.addClause(\"name = 'alice'\")           // accepted\n\n  // A URL-shaped fragment is NOT flagged\n  qb.addClause(\"redirect=http://evil/?p=1\") // accepted\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\nThe program ends with accepted=3 rejected=2, and each rejection writes a JSON warning line to stderr naming the threat (LDAP_INJECTION, NOSQL_INJECTION). 'sanitized' screens for injection patterns; it does not judge whether a URL is trustworthy, so validate redirect targets yourself.\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":"#!ek9\ndefines 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","method","method validation","object protection","sanitized"],"primaryTopics":["sanitized method parameter","query builder validation","diverse injection categories"],"typicalErrors":[{"error":"E07910","correct":"-> fragment as sanitized String","incorrect":"-> fragment as sanitized Integer","explanation":"The sanitized modifier can only be applied to String parameters. Injection attacks travel as text, so a non-String type has nothing to clean. See ek9 -h E07910 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 and keeps the first set result. Pure and concise — no class hierarchy needed.\n\nHANDLER INTERFACE\n  defines function\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 calls every handler in the list and uses assign-if-unset 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. Note that the walk itself does not stop: every handler is still invoked, and only the first set result is kept. That matters if a handler has side effects.\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 assign-if-unset.","ek9Example":"#!ek9\ndefines 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 E08050.\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":"#!ek9\ndefines 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":["E08050","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":"E08050","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\"\n      else\n        label: \"cold\"","incorrect":"      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\"","explanation":"Without a final else and without a default value at declaration, label is uninitialised when celsius is below COOL_THRESHOLD. Either provide a default at declaration (Pattern 1) or add the final else (Pattern 2). See ek9 -h E08050 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":"#!ek9\ndefines 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_new_construct","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 pure abstract\n      <- rtn as Boolean?\n\n    protocol() as pure\n      <- rtn as String: \"generic\"\n\n    default operator ?\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\n    default operator ?\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":"#!ek9\ndefines 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":"E05160","correct":"connect() as pure abstract","incorrect":"connect() as abstract","explanation":"An override cannot be 'pure' when the abstract method it implements is not 'pure'. Declare the abstract method 'as pure abstract' so every implementation can be pure. See ek9 -h E05160 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_new_construct","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":"#!ek9\ndefines 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_new_construct","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":"#!ek9\ndefines 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_new_construct","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: Many built-in types are closed and can never be extended - the generics List, Dict, Optional, Result, PriorityQueue and others. (A few built-ins, such as String and Integer, ARE declared 'as open'; 'ek9 -h <Type>' shows which.)\n  StringList extends List of String   // E05030 - List is closed\n    default StringList()\n\nFix: use COMPOSITION instead — hold the built-in as a field:\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\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 (a closed built-in type) you MUST use composition. Those 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":"#!ek9\ndefines 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":"E05030","correct":"    StringCollection\n      items as List of String: List() of String","incorrect":"    StringCollection extends List of String\n      items as List of String: List() of String","explanation":"List is closed (not open to be extended); use composition and hold the List as a property. See ek9 -h E05030 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__. Declare 'operator $' on a user-defined record to control what $ returns.\n\nPRODUCT RECORD WITH $ OPERATOR\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\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":"#!ek9\ndefines 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 define 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). Declare 'operator $$' on a user-defined record to control what $$ returns - typically a JSON object built from the record's fields. It has no parent version, so it takes no 'override'.\n\nCUSTOMER RECORD WITH $$\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(\"id\", $$id) + JSON(\"firstName\", $$firstName) + JSON(\"lastName\", $$lastName)\n\n    override operator ? as pure\n      <- rtn as Boolean: id?\n\nJSON(name, value) builds a one-property object, $$ on each field gives its JSON value, and + merges the objects.\n\nUSAGE\n  customer <- Customer(1001, \"Ada\", \"Lovelace\")\n  jsonValue <- $$customer\n  stdout.println(`JSON: ${jsonValue}`)\n  // Output: JSON: {\"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\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":"#!ek9\ndefines module qa.operators.dollardollarjsoncustomer\n\n  defines record\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(\"id\", $$id) + JSON(\"firstName\", $$firstName) + JSON(\"lastName\", $$lastName)\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":"#!ek9\ndefines 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":"E07570","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 E07570 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(\"invoiceNumber\", JSON(invoiceNumber)) + JSON(\"amount\", JSON(amount)) + JSON(\"paid\", JSON(paid))\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  // Output: Invoice as JSON: {\"invoiceNumber\":\"INV-2024-0042\",\"amount\":125.5,\"paid\":false}\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":"#!ek9\ndefines 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(\"invoiceNumber\", JSON(invoiceNumber)) + JSON(\"amount\", JSON(amount)) + JSON(\"paid\", JSON(paid))\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(\"invoiceNumber\", JSON(invoiceNumber)) + JSON(\"amount\", JSON(amount)) + JSON(\"paid\", JSON(paid))","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  // C: x gets the old value\n  x = counter++;\n  // C: x gets the new value\n  x = ++counter;\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":"#!ek9\ndefines 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":"E07950","correct":"      visitorCount++\n      stdout.println(`After three ++: ${visitorCount}`)","incorrect":"      current <- visitorCount++\n      stdout.println(`After three ++: ${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 E07950 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":"#!ek9\ndefines 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, but as an expression that yields a value (EK9 makes it statement-only). 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":"#!ek9\ndefines 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\nThree 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 guards.\n\nSee Q1259 for Integer ++/--. See Q1260 for Float ++/--. See Q1261 for Date ++/--. See Q220 for full enumeration navigation.","ek9Example":"#!ek9\ndefines 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++\n      stdout.println(`past last is set: ${state?}`)","incorrect":"state.next()\n      stdout.println(`past last is set: ${state?}`)","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': :=? (assign-if-unset). 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 ASSIGN-IF-UNSET, 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 assign-if-unset (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 assign-if-unset. See Q1265 for Date. See Q1266 for custom record. See Q1226 for the contrast between :=? and <?.","ek9Example":"#!ek9\ndefines 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","assign-if-unset","conditional assignment","default value","fallback","unset"],"primaryTopics":["String assign-if-unset","default value pattern",":=? operator"],"typicalErrors":[{"error":"E08095","correct":"      greeting as String: String()","incorrect":"      greeting as String: \"Hi\"","explanation":":=? only assigns when the target is unset. A variable declared with a value is always set, so the compiler rejects the assign-if-unset as one that can never apply (E08095) - declare it unset, as String(), when it should take the default. See ek9 -h E08095 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 assign-if-unset 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 :=? assign-if-unset 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 assign-if-unset. See Q1265 for Date. See Q1266 for custom record. See Q1226 for the contrast between :=? and <?.","ek9Example":"#!ek9\ndefines 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","assign-if-unset","default value","initialise once","lazy init","retry counter"],"primaryTopics":["Integer assign-if-unset","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 assign-if-unset — 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 :=? assign-if-unset 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 assign-if-unset. See Q1264 for Integer. See Q1266 for custom record. See Q1226 for the contrast between :=? and <?.","ek9Example":"#!ek9\ndefines 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","assign-if-unset","audit","first occurrence","first seen","set once"],"primaryTopics":["Date assign-if-unset","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 assign-if-unset — 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 class method that sets a field once?","Give me a UserSession example using assign-if-unset on a token field.","Use :=? on a custom class field for set-once semantics.","Show me a method that initialises a class field with :=? only when unset."],"answer":"The :=? assign-if-unset 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 ASSIGN-IF-UNSET 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 assign-if-unset. See Q1264 for Integer. See Q1265 for Date. See Q1226 for the contrast between :=? and <?.","ek9Example":"#!ek9\ndefines 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","assign-if-unset","class field","first value wins","session token","set once"],"primaryTopics":[":=? on class 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 assign-if-unset — 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 assign-if-unset :=? 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 assign-if-unset 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() 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\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 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":"#!ek9\ndefines 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":[":=?","assign-if-unset","find-first","first match","guard","loop","no break","no return"],"primaryTopics":[":=? assign-if-unset 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 :=? assign-if-unset 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 no-arg constructor that gives each field an unset value: 'Pair() as pure' with 'first :=? A()' and 'second :=? B()'. A public 'default Pair() as pure' is rejected while 'first as A?' and 'second as B?' are uninitialised (E07175, plus E08060 for each field); 'default private Pair() as pure' is the alternative\n2. MUST have a parameterised constructor with both type params\n3. The parameterised constructor MUST be PUBLIC — a private one triggers E06060. A protected constructor is never allowed (E06060). Only the no-arg constructor may be private, either 'default private Pair() as pure' or a hand-written 'private Pair() as pure' with a body\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    Pair() as pure\n      first :=? A()\n      second :=? B()\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 Q1286 for why generic constructors must be public. See Q1243 for two-param Transformer example.","ek9Example":"#!ek9\ndefines 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\n        ->\n          a as A","incorrect":"      private Pair() as pure\n        ->\n          a as A","explanation":"A generic type's parameterised constructor must be public so the type can be instantiated (and inferred) with any valid type parameters; making it private triggers E06060, and a protected constructor is never allowed. Only the no-arg constructor may be private. 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  defines function\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    ->\n      items as List of String\n      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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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. Once a package declares 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 whose capabilities list omits the network capability literally cannot open a socket in its non-dev code; the compiler refuses to emit the bytecode. Enforcement starts when the capabilities block is declared - a package with no block at all is not checked (see Q1276).\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 need no capabilities, so a reviewer should expect none to be declared; the compiler restricts a package only once it has a capabilities block (see Q1276). 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":"#!ek9\ndefines module qa.packagecapabilities.whyrationale\n\n  defines package\n    version <- 1.0.0-0\n    description <- \"Publishable package declaring the Stdout capability it uses\"\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, TCPReadyHandler, TCPReadyObserver\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":"#!ek9\ndefines 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\nThe library declares Stdout, uses it internally, and exports pure functions:\n  defines module my.library\n    defines package\n      description <- \"A small library\"\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\nThe consumer module adds a 'references' entry for my.library::compute, calls compute() and never mentions Stdout itself, so it only needs its own capabilities - nothing is inherited from my.library:\n  defines package\n    description <- \"A library consumer\"\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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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/ is the package directory\n    - main.ek9 is publishable, subject to capability rules\n    - helper.ek9 is publishable, subject to capability rules\n    - dev/tests.ek9 is EXEMPT - it can use Stdout, File, EnvVars freely\n    - dev/testFixtures.ek9 is 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":"#!ek9\ndefines 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. NON-PUBLISHABLE PACKAGES AND DEV CODE:\n   - E12010 is never raised there, because they are not part of the supply chain, so there is no capability error to fix.\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":"#!ek9\ndefines 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  choice as String: isReady <- \"a\" : \"b\"   // explicit type + ternary\n\nFIELD DECLARATION FORMS THAT FAIL WITH E04050\n  Holder\n    isReady <- true\n    names <- List() of String?        // ERROR E04050: '?' invoked on the List\n    choice <- isReady <- \"a\" : \"b\"    // ERROR E04050: an EK9 ternary - write 'choice as String: isReady <- \"a\" : \"b\"'\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":"#!ek9\ndefines 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 names not 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  head as String: items.getOrDefault(0, String())\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 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                   // arithmetic (E04050)\n  head <- items.getOrDefault(0, String())     // method call (E04050)\n\nThe fix is mechanical: change '<-' to 'as <inferred-type>:' with no other edits.\n\n  total as Float: price * quantity            // OK\n  head as String: items.getOrDefault(0, String())  // OK\n\nSee Q585 for field initialisation rules and Q1282 for the specific is-set-operator trap.","ek9Example":"#!ek9\ndefines 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?","Can a generic type constructor be private?","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":"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 - a protected no-arg would still let a subclass construct an uninitialised instance.\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.","ek9Example":"#!ek9\ndefines 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","access","constant","constructor","factory method","generic","generic type","instantiation","parameterised type","private","private constructor","protected constructor","public","public constructor","type-parameter","visibility"],"primaryTopics":["E06060 error","generic type constructors","constructor access on generics","private generic constructor","E06060","generic constructor access"],"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 more than one constructor, but the set is fixed: a no-argument constructor and the PARAMETERISED (inferred) constructor, whose number of parameters equals the number of type parameters (anything else is E06030). The access rule is nuanced, not absolute: the parameterised constructor must be public so the compiler can infer type arguments and instantiate from any context; the no-argument constructor may be private (either 'default private' or a hand-written 'private' one with a body) 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 private no-arg constructor.\n\nCORRECT: TWO 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      default operator ?\n\nTwo constructors: no-argument and single-argument (one type parameter, one argument). Both public. Adding a third, two-argument constructor such as (initialItem as T, tag as String) is E06030 - for type inference to work the number of generic and constructor parameters must be the same; set the label through a method or a factory function instead. 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\n      default operator ?\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      -> candidate as String\n      <- rtn as Box of String: Box() of String\n      if candidate?\n        rtn: Box(candidate)\n\nThe return value starts as an empty Box so it is set on every path (an uninitialised 'Box of String?' set only inside the if is E08050).\n\nUse a private no-arg constructor to forbid EMPTY construction; use a factory to control VALUED construction with validation, defaulting, or logging.\n\nSee Q1286 for the single-constructor form and the private no-arg escape hatch.","ek9Example":"#!ek9\ndefines 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: a generic's parameterised constructor is public by rule (only the no-arg constructor may be private, and protected is never allowed), 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":"'protected' is never allowed on a generic type's constructor, so it fires E06060. 'private' is rejected only on a parameterised constructor; any no-arg constructor may be private. 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":"#!ek9\ndefines 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      //An injected component is always set, so this component is too - there is nothing to check\n      override operator ? as pure\n        <- rtn as Boolean: true\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":"#!ek9\ndefines 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  defines service\n    PublicApi for :/api open\n      health() as GET for :/health\n        <- response as HTTPResponse: healthReply()\n    SecureApi for :/api constrain by JwtGate\n      data() as GET for :/data\n        <- response as HTTPResponse: dataReply()\n\nOPERATION-LEVEL POSTURE\nWhen the service has no posture, each operation must declare its own:\n  defines service\n    MixedApi for :/api\n      health() as GET for :/status open\n        <- response as HTTPResponse: healthReply()\n      data() as GET for :/data constrain by JwtGate\n        <- response as HTTPResponse: dataReply()\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":"#!ek9\ndefines 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 for :/api/public open\n\n      health() as GET for :/health\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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 for :/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 Bytes: Bytes().withUtf8(`{\"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 for :/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 Bytes: Bytes().withUtf8(`{\"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 for :/api/mixed\n\n      health() as GET for :/health open\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(\"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 Bytes: Bytes().withUtf8(`{\"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":["E12050","service security","security posture"],"typicalErrors":[{"error":"E12050","correct":"    PublicApi for :/api/public open","incorrect":"    PublicApi for :/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  defines service\n    MyApi for :/api constrain by NonExistentGate   // ERROR E12060\n      resource() as GET for :/resource\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(\"ok\")\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-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\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, then reference it from the service:\n  defines function\n    JwtGate is SecurityGate\n      -> context as HTTPContext\n      <- rtn as HTTPContext: context\n\n  defines service\n    MyApi for :/api constrain by JwtGate\n      resource() as GET for :/resource\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(\"ok\")\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-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\nSee Q1292 for security posture overview. See Q1294 for constraint type validation. See Q1295 for duplicate constraint roles.","ek9Example":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"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":["E12060","constrain unresolved","constraint resolve"],"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  defines function\n    MyGate is SecurityGate\n      -> context as HTTPContext\n      <- rtn as HTTPContext: context\n\nCORSPolicy: validates cross-origin requests.\n  defines function\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, such as this function:\n  defines function\n    NotAGate()\n      <- rtn as String: \"hello\"\nCannot be used as a constraint. A service declared as `MyApi for :/api constrain by NotAGate` is rejected with 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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"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 for :/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 Bytes: Bytes().withUtf8(`{\"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":["E12062","constraint invalid"],"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, write 'constrain by JwtGate'. Or only CORS without authentication (unusual), write 'constrain by MyCors'.\n\nSee Q1292 for security posture overview. See Q1293 for constraint resolution. See Q1294 for constraint type validation.","ek9Example":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"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":["E12064","duplicate constraint","constraint role"],"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 — EACH TYPE COPIES AT ITS OWN LEVEL\nA built-in copies at its own level: a String or Integer copies its value (Pattern 2 above), a List gets its own container holding the SAME elements. The generated `default operator :=:` on your own type is SHALLOW: each field of the target refers to the same object as the source's, so a snapshot taken with it shares every mutable field - for a deep or selective snapshot write `operator :=:` yourself (see Q2100). The same question applies to copy constructors — whether they 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":"#!ek9\ndefines 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":[],"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.$() 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 (e.g. super.$())\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\nSAMENESS IS DECLARED ONCE PER HIERARCHY\nThe comparator <=> (or ==) and its #? are the type's sameness, and a hierarchy declares them exactly once: a child that inherits sameness cannot redeclare <=>, == or #? (E07182/E07184), and ==, <>, <, <=, >, >= are always generated from <=>. So when the child's own fields must take part in comparison, put the sameness on the CHILD (the base declares none) and compare the inherited fields first:\n  operator <=> as pure\n    -> other as ChildRecord\n    <- rtn as Integer: parentField <=> other.parentField\n    if rtn == 0\n      rtn: childField1 <=> other.childField1\n\n  operator #? as pure\n    <- rtn as Integer: #?parentField xor #?childField1\n\nIf instead the base declares the sameness, every child inherits it unchanged; a finer ordering is then a comparator function given to sort.\n\nOPERATOR OVERRIDE CHAINING WITH super.<op>(...)\nOther operators, such as $, are overridden and can compose the parent's result. For $ the convention is to append the child's representation:\n  override operator $ as pure\n    <- rtn as String: `${super.$()} ChildRecord(${childField1}, ${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":"#!ek9\ndefines 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      //No sameness here: SimpleRecord declares it (sameness is declared once per hierarchy)\n      default operator ?\n\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      - Sameness ('<=>' + '#?') is declared ONCE per hierarchy; the base declares none,\n        so this child declares it over the inherited AND its own fields.\n      - 'override operator $' is mandatory (E05120 otherwise) and\n        'super.$()' composes the parent's string 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      operator <=> as pure\n        -> other as SimpleRecord\n        <- rtn as Integer: anything <=> other.anything\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      operator #? as pure\n        <- rtn as Integer: #?anything 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 child's <=> compares the inherited field, then its own 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 other operator inherited from the parent - but never to the sameness ('<=>', '==', '#?'), which a hierarchy declares only once (E07182/E07184). See ek9 -h E05120 for details."},{"error":"E50010","correct":"SimpleRecord extends BaseRecord","incorrect":"SimpleRecord extends BaseRecords","explanation":"The type named after 'extends' must be a record that exists in scope; a misspelt or undeclared name (and a class cannot stand in for a record, because classes extend classes and records extend records) cannot be found. See ek9 -h E50010 for details."},{"error":"E07290","correct":"this.check :=: check\n\n      operator <=> as pure","incorrect":"this.check :=: check\n\n      describe()\n        <- rtn as String: \"simple\"\n\n      operator <=> as pure","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\n      default operator ?\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":"#!ek9\ndefines 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 <- List() of Item\n  key <- (preparedItems) is MutexKey of Integer as function\n    preparedItems += extractSnapshot()   //all locked work here\n  require sharedLock.enter(key)\n\n  processed <- cat preparedItems | async with processItem | collect as List of Item   //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":"#!ek9\ndefines 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\n      default operator ?\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\nA CONSISTENT ORDER OVER DISTINCT LOCK FIELDS IS ACCEPTED\nNesting two DIFFERENT lock fields in one direction everywhere ('always lockA before lockB') compiles and runs: the compiler can prove the order, so there is no cycle. Only a site that contradicts that order (E08252) or nesting two interchangeable same-type locks (E08255) is rejected. The compiler checks the order itself - it does not rely on developer discipline, so a later refactoring that adds a reverse-order site is caught immediately.\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 (the Counter field-on-class pattern). See Q1298 for E08252 cycle errors. See Q1299 for E08253 cross-thread errors.","ek9Example":"#!ek9\ndefines 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; a consistent lock order over distinct locks is accepted and any contradicting order is rejected by the compiler, so it does not depend on convention.","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, join, split) 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 - when it comes BEFORE the buffering stage. A head after join or split cannot stop them: cat stdin | split with isBlank | head 2 is still E10050.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 Q862 for the per-component injection-field limit (E11040). See Q311 for quality checks.","ek9Example":"#!ek9\ndefines 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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"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 Bytes: Bytes().withUtf8(`{\"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":"byName() as GET for :/{name}","explanation":"Two GET methods mapping to :/{id} and :/{name} 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 referencing a type and then defining a local class with the same name 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.\n\nNote that a local construct named after a BUILT-IN type (a class called List, say) is refused earlier, with E01090 (the name of a built-in construct cannot be given a second meaning), before references are even checked. E03010 is for the names of types that come from another module; a single file can show it only by referencing a type of its own module (references qa.syntaxrules.referencecollision::ItemList while also defining ItemList). 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":"#!ek9\ndefines 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'. Also referencing a type called\n  //ItemList (from another module) would trigger E03010 CONSTRUCT_REFERENCE_CONFLICT;\n  //naming this class 'List' is E01090 instead, because List is built in.\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","incorrect":"  references\n    org.ek9.lang::List\n    qa.syntaxrules.referencecollision::ItemList","explanation":"Referencing 'ItemList' via references and then defining a local class also named 'ItemList' makes the short name ambiguous - neither the reference nor the local definition takes priority, so EK9 raises E03010 (here the referenced type stands in for one from another module). Rename the local construct so each name resolves to exactly one symbol, or drop the reference and use the fully qualified name 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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() as pure\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 inside a regular method raises E05060. The same misuse in a property initialiser is reported as E05070 (this) or E05080 (super), and in a function as E05070 (this) or E50020 (super).\n\nEK9 distinguishes the CALL syntax from the member-ACCESS syntax:\n- `this()` calls another constructor of this class (constructors only)\n- `super()` calls the parent constructor (constructors only)\n- `this.x` accesses a member of this instance (anywhere in the class)\n- `super.x` accesses 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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      stdout <- Stdout()\n","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":"#!ek9\ndefines 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":"    GreetApp\n      register ConsoleGreeter() as Greeter","incorrect":"    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 NEAREST matching operator above it first - the super's, or one further up when the classes in between add no fields and declare none - 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 naming the first operator it cannot chain to (<=>).\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. Likewise a derived class that ADDS fields declares its own '$', '$$' and ':=:' rather than inheriting the base's, which cannot know the new fields (E07238) - the bare 'default operator' in Circle does that.\n\nSee Q1069 for mixing default operator with a custom override. See Q577 for override operator basics.","ek9Example":"#!ek9\ndefines 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","E07238","base","default","extends","inherit","operator","override","super"],"primaryTopics":["default operator requires super operator","E07190","operator inheritance"],"typicalErrors":[{"error":"E07190","correct":"        this.name :=: name\n\n      default operator","incorrect":"        this.name :=: name\n\n      default operator ?","explanation":"A derived 'default operator' generates operators that call the super's matching operator first. When the base class (Shape) defines only 'operator ?' and nothing else, there is nothing for the derived default to chain to, so the compiler reports E07190 for the first missing operator it needs (<=>). 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."},{"error":"E07238","correct":"        this.radius :=: radius\n\n      default operator","incorrect":"        this.radius :=: radius\n\n      default operator ?","explanation":"Circle adds a field (radius) but declares only '?', so it would INHERIT Shape's '$', '$$' and ':=:' - operators written for Shape's fields, which cannot know about radius: the string and JSON would silently leave it out and a copy would copy only half the object. A type that adds fields declares its own '$', '$$' and ':=:' (as it must declare '?', E07235); the bare 'default operator' declares them all, each calling Shape's first. See ek9 -h E07238 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":"#!ek9\ndefines 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":"this.y :=: y\n\n      default operator","incorrect":"this.y :=: y\n\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":"#!ek9\ndefines module qa.web.serviceprotected\n\n  defines service\n\n    Greeting for :/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 Bytes: Bytes().withUtf8(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: \"Hello from the EK9 service\"","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":"#!ek9\ndefines 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","incorrect":"Account\n      balance as Float: 0.0","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":"#!ek9\ndefines 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}`)\n      else\n        throw Exception(\"unexpected\")","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 Q997 for the general '<- rtn' return mechanism.","ek9Example":"#!ek9\ndefines 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, and return Boolean.\n\nFor comparison with the type ITSELF you write neither: the type declares its sameness once - '<=>' for an ordered type, or '==' for an equatable-only type - with a matching '#?', and the compiler generates '<>' (and ==, <, <=, >, >= from '<=>'). Hand-writing a self '<>' is E07181. You declare 'operator <>' by hand only against an unrelated type, as Score does with Integer here. 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":"#!ek9\ndefines 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      //The sameness: ==, <>, <, <=, >, >= with another Score are generated from it,\n      //so '<>' for Score is never written by hand\n      operator <=> as pure\n        -> other as Score\n        <- rtn as Integer: points <=> other.points\n\n      operator #? as pure\n        <- rtn as Integer: #?points\n\n      //THE FIX: EK9's not-equal operator is '<>', NOT '!='.\n      //Declaring 'operator !=' would trigger E07640.\n      //(Here '<>' compares with an unrelated type, Integer, so it is written by hand.)\n      operator <> as pure\n        -> threshold as Integer\n        <- rtn as Boolean: points <> threshold\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":["!=","<>","E07181","E07640","comparison","declaration","equal","inequality","neq","not","operator","sameness"],"primaryTopics":["not-equal operator","E07640","operator declaration"],"typicalErrors":[{"error":"E07640","correct":"operator <> as pure\n        -> threshold as Integer\n        <- rtn as Boolean: points <> threshold","incorrect":"operator != as pure\n        -> threshold as Integer\n        <- rtn as Boolean: points <> threshold","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). For comparison with the type itself, write neither: declare the sameness ('<=>' or '==') and '<>' is generated. See ek9 -h E07640 for details."},{"error":"E07181","correct":"      operator #? as pure\n        <- rtn as Integer: #?points","incorrect":"      operator #? as pure\n        <- rtn as Integer: #?points\n\n      operator <> as pure\n        -> other as Score\n        <- rtn as Boolean: points <> other.points","explanation":"'<>' with the type itself is generated from its sameness (here '<=>'), so declaring it by hand for Score is E07181 (COMPARISON_GENERATED_FROM_SAMENESS). Delete it - 'a <> b' already works. See ek9 -h E07181 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":"A type's logical-NOT (and bitwise complement) operator is DECLARED with the '~' symbol. When you declare 'operator not' on a class, record, or trait the compiler raises E07650 (BAD_NOT_OPERATOR) and tells you to use '~'.\n\nDefine the operator with '~'; on a custom type it must be pure, take no arguments, and return the SAME type (so 'Switch' returns a negated 'Switch'):\n  operator ~ as pure\n    <- rtn as Switch: Switch(~ active)\n\nAt a CALL SITE both forms negate: '~value' and 'not value' are the same operator, on the built-in Boolean and on any type that declares '~' (so 'not on' calls Switch's '~'). Only the C-style '!value' does not exist in EK9. ('not' also prefixes the membership tests 'not contains' / 'not in'.)\n\nSee Q244 for boolean and bitwise operators. See Q903 for operators that do NOT exist in EK9.","ek9Example":"#!ek9\ndefines 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 Switch (the same type).\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}`)\n\n      //At a call site 'not' is the word form of the same '~' operator\n      alsoOff <- not on\n      stdout.println(`Not on:    ${alsoOff}`)\n\n      enabled <- true\n      stdout.println(`not enabled: ${not enabled}`)","migrationContext":"Java/C/C++/JavaScript/Rust: '!' is logical NOT. Python: 'not' keyword is logical NOT. Kotlin: '!' plus an overloadable 'not()' operator function. EK9: a type declares negation as 'operator ~' ('operator not' is E07650); at call sites 'not x' and '~x' both negate, and '!' does not exist.","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":"Declaring 'operator not' on a type raises E07650. Declare the operator as '~', which is EK9's logical-NOT and bitwise-complement operator; callers may then write either '~value' or 'not 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: += adds to the collection (POST), -= removes from it (DELETE), :^: replaces (PUT), :~: merges (PATCH), and ? answers a HEAD request (headers and status only; inside a service it is not the is-set check). 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":"#!ek9\ndefines 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 for :/items open\n\n      // GET /items — list all items\n      listAll() for :/\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`[\"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 += for :/\n        -> content as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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 += for :/\n        -> content as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse","incorrect":"      operator * for :/\n        -> content as String :=: CONTENT\n        <- response as HTTPResponse: () with trait 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."},{"error":"E07670","correct":"      operator += for :/","incorrect":"      operator + for :/","explanation":"A service adds to its collection with '+=' (POST) and removes with '-=' (DELETE); '+' and '-' are not service operators (one form each), so 'operator +' is E07670. The editing tools rewrite it to '+='. 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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"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":"<-\n          response as HTTPResponse: (capturedId: id) with trait HTTPResponse\n            override content()\n              <- rtn as Bytes: Bytes().withUtf8(`{\"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 ?","incorrect":"<-\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 or HEADER 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 or HEADER 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 or header 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\"\nA PATH binding may omit the name when the parameter has the same name as the {variable} in the URI (id as String :=: PATH for :/{id}); a name is only needed to bind a differently named parameter.\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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"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":"->\n          term as String :=: QUERY","explanation":"A QUERY (or HEADER) binding must state the wire name it reads from. Without it, the compiler cannot tell which query key or header 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":"#!ek9\ndefines 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 for :/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().toText(\"utf-8\")) with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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 += for :/\n        -> body as String :=: CONTENT\n        <- response as HTTPResponse: (received: body) with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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":"#!ek9\ndefines module qa.web.servicemethodbody\n\n  defines service\n\n    StatusService for :/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 Bytes: Bytes().withUtf8(`{\"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 Bytes: Bytes().withUtf8(`{\"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":"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  defines service\n    EchoService for :/echo open\n      handle() as GET for :/\n        -> request as HTTPRequest :=: REQUEST\n        <- response as HTTPResponse: reply(request)\nThe single parameter receives the whole request.\n\nINCORRECT PATTERN\n  defines service\n    EchoService for :/echo open\n      handle() as GET for :/\n        -> request as String :=: REQUEST  // ERROR E07770\n        <- response as HTTPResponse: reply(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":"#!ek9\ndefines 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 for :/echo open\n\n      handle() as GET for :/\n        -> request as HTTPRequest :=: REQUEST\n        <- response as HTTPResponse: (body: request.content().toText(\"utf-8\")) with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`{\"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 carries the whole request, so mixing it with individually-bound parameters would bind the same request twice and create two sources of truth for the same data.\n\nCORRECT PATTERN\n  process() as POST for :/process\n    -> request as HTTPRequest :=: REQUEST\n    <- response as HTTPResponse: ...\nRead the body from the request inside the method body via request.content(). HTTPRequest offers only the body (content()), no path, query or header accessors, so an operation that needs a path, query or header value binds it with :=: PATH, QUERY or HEADER and does not take the HTTPRequest.\n\nINCORRECT PATTERN\n  process() as POST for :/process\n    ->\n      request as HTTPRequest :=: REQUEST\n      body as String :=: CONTENT\n    <- response as HTTPResponse: ...\nThe extra body parameter alongside the full request raises E07780.\n\nSee Q202 for parameter binding. See Q868 for valid path-parameter types.","ek9Example":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"received\": \"${request.content().toText(\"utf-8\")}\"}`)\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 the body from the request with request.content(); bind path, query and header values with PATH, QUERY and HEADER instead.\")","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  defines service\n\n    DataService for :/data open\n\n      info() as GET for :/data\n        -> ctx as HTTPContext :=: CONTEXT\n        <- response as HTTPResponse: (captured: ctx) with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`${captured}`)\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-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\nINCORRECT PATTERN\n  defines service\n\n    DataService for :/data open\n\n      info() as GET for :/data\n        -> ctx as String :=: CONTEXT\n        <- response as HTTPResponse: (captured: ctx) with trait HTTPResponse\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`${captured}`)\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-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`{\"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. Adding individual :=: QUERY or :=: PATH bindings alongside it raises E07792: a method has exactly one binding strategy.\n\nHTTPContext carries security and connection metadata, not the request data: principal(), isAuthenticated(), isSecure(), remoteAddress(), requestId(), header(name), contentType(), cookie(name) and withPrincipal(identity). It has NO query, path or body accessors.\n\nSo choose by what the method needs:\n1. Use HTTPContext ALONE when the method needs only that metadata (who is calling, request id, a header or cookie).\n2. When the method needs query, path or body values, bind them as 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":"#!ek9\ndefines 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 request context via :=: CONTEXT and uses ONLY\n      that single parameter. HTTPContext carries metadata (principal,\n      headers, cookies, request id), not query or path values. A method\n      that needs those binds them with :=: QUERY/PATH instead and takes\n      no HTTPContext - mixing the two raises E07792.\n    -?>\n    SearchService for :/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 Bytes: Bytes().withUtf8(`{\"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(\"It carries metadata only: principal, headers, cookies, request id.\")\n      stdout.println(\"To read query or path values, bind them with :=: QUERY or :=: PATH\")\n      stdout.println(\"and do not take HTTPContext at all (mixing them is E07792).\")","migrationContext":"Java Spring: you may freely combine HttpServletRequest with @RequestParam/@PathVariable in the same handler - no compiler objection, JAX-RS allows @Context HttpServletRequest beside @QueryParam. EK9 instead makes the mix a compile-time error (E07792): 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) must be the only parameter, so adding a :=: QUERY binding alongside it triggers E07792. HTTPContext has no query accessor; a method that needs the query value binds it with :=: QUERY and does not take HTTPContext. 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\nOn a service method such as `info() as GET for :/info`, bind the parameter as `-> ctx as HTTPContext :=: CONTEXT`. HTTPContext bound with CONTEXT gives access to the entire request context.\n\nIf you only need a single query value, bind a simple type instead. On a method such as `search() as GET for :/search`, write `-> 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":"#!ek9\ndefines 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 for :/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 Bytes: Bytes().withUtf8(`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 Bytes: Bytes().withUtf8(`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":"#!ek9\ndefines 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  floored <- onlinePrice >? FLOOR_PRICE    (a floor: never below FLOOR_PRICE)\n  cap     <- requested <? MAX_LIMIT        (a cap: never above 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":"#!ek9\ndefines 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 maximum (>?) gives the larger operand, so it acts as a FLOOR.\n      Replacing onlinePrice with a literal would trigger E08094.\n    -?>\n    flooredPrice() as pure\n      -> onlinePrice as Integer\n      <- floored as Integer: onlinePrice >? FLOOR_PRICE\n\n    <?-\n      Correct: coalescing minimum (<?) gives the smaller operand, so it acts as a CAP.\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(`Floored price: ${flooredPrice(75)}`)\n      stdout.println(`Floored price: ${flooredPrice(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":"floored as Integer: onlinePrice >? FLOOR_PRICE","incorrect":"floored as Integer: 100 >? 200","explanation":"Both operands of the <? coalescing minimum are literals, so the result is fixed at compile time (always 200) - 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":"#!ek9\ndefines 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":"register RedisCache() as Cache","incorrect":"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":"#!ek9\ndefines 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() as pure\n      -> values as List of Integer\n      <- rtn as String?\n      stepTotal <- sumValues(values)\n      step002 <- stepTotal + stepTotal\n      step003 <- step002 + stepTotal\n      step004 <- step003 + stepTotal\n      step005 <- step004 + stepTotal\n      step006 <- step005 + stepTotal\n      step007 <- step006 + stepTotal\n      step008 <- step007 + stepTotal\n      step009 <- step008 + stepTotal\n      step010 <- step009 + stepTotal\n      step011 <- step010 + stepTotal\n      step012 <- step011 + stepTotal\n      step013 <- step012 + stepTotal\n      step014 <- step013 + stepTotal\n      step015 <- step014 + stepTotal\n      step016 <- step015 + stepTotal\n      step017 <- step016 + stepTotal\n      step018 <- step017 + stepTotal\n      step019 <- step018 + stepTotal\n      step020 <- step019 + stepTotal\n      step021 <- step020 + stepTotal\n      step022 <- step021 + stepTotal\n      step023 <- step022 + stepTotal\n      step024 <- step023 + stepTotal\n      step025 <- step024 + stepTotal\n      step026 <- step025 + stepTotal\n      step027 <- step026 + stepTotal\n      step028 <- step027 + stepTotal\n      step029 <- step028 + stepTotal\n      step030 <- step029 + stepTotal\n      step031 <- step030 + stepTotal\n      step032 <- step031 + stepTotal\n      step033 <- step032 + stepTotal\n      step034 <- step033 + stepTotal\n      step035 <- step034 + stepTotal\n      step036 <- step035 + stepTotal\n      step037 <- step036 + stepTotal\n      step038 <- step037 + stepTotal\n      step039 <- step038 + stepTotal\n      step040 <- step039 + stepTotal\n      step041 <- step040 + stepTotal\n      step042 <- step041 + stepTotal\n      step043 <- step042 + stepTotal\n      step044 <- step043 + stepTotal\n      step045 <- step044 + stepTotal\n      step046 <- step045 + stepTotal\n      step047 <- step046 + stepTotal\n      step048 <- step047 + stepTotal\n      step049 <- step048 + stepTotal\n      step050 <- step049 + stepTotal\n      step051 <- step050 + stepTotal\n      step052 <- step051 + stepTotal\n      step053 <- step052 + stepTotal\n      step054 <- step053 + stepTotal\n      step055 <- step054 + stepTotal\n      step056 <- step055 + stepTotal\n      step057 <- step056 + stepTotal\n      step058 <- step057 + stepTotal\n      step059 <- step058 + stepTotal\n      step060 <- step059 + stepTotal\n      step061 <- step060 + stepTotal\n      step062 <- step061 + stepTotal\n      step063 <- step062 + stepTotal\n      step064 <- step063 + stepTotal\n      step065 <- step064 + stepTotal\n      step066 <- step065 + stepTotal\n      step067 <- step066 + stepTotal\n      step068 <- step067 + stepTotal\n      step069 <- step068 + stepTotal\n      step070 <- step069 + stepTotal\n      step071 <- step070 + stepTotal\n      step072 <- step071 + stepTotal\n      step073 <- step072 + stepTotal\n      step074 <- step073 + stepTotal\n      step075 <- step074 + stepTotal\n      step076 <- step075 + stepTotal\n      step077 <- step076 + stepTotal\n      step078 <- step077 + stepTotal\n      step079 <- step078 + stepTotal\n      step080 <- step079 + stepTotal\n      step081 <- step080 + stepTotal\n      step082 <- step081 + stepTotal\n      step083 <- step082 + stepTotal\n      step084 <- step083 + stepTotal\n      step085 <- step084 + stepTotal\n      step086 <- step085 + stepTotal\n      step087 <- step086 + stepTotal\n      step088 <- step087 + stepTotal\n      step089 <- step088 + stepTotal\n      step090 <- step089 + stepTotal\n      step091 <- step090 + stepTotal\n      step092 <- step091 + stepTotal\n      step093 <- step092 + stepTotal\n      step094 <- step093 + stepTotal\n      step095 <- step094 + stepTotal\n      step096 <- step095 + stepTotal\n      step097 <- step096 + stepTotal\n      step098 <- step097 + stepTotal\n      step099 <- step098 + stepTotal\n      step100 <- step099 + stepTotal\n      step101 <- step100 + stepTotal\n      step102 <- step101 + stepTotal\n      step103 <- step102 + stepTotal\n      step104 <- step103 + stepTotal\n      step105 <- step104 + stepTotal\n      step106 <- step105 + stepTotal\n      step107 <- step106 + stepTotal\n      step108 <- step107 + stepTotal\n      step109 <- step108 + stepTotal\n      step110 <- step109 + stepTotal\n      step111 <- step110 + stepTotal\n      step112 <- step111 + stepTotal\n      step113 <- step112 + stepTotal\n      step114 <- step113 + stepTotal\n      step115 <- step114 + stepTotal\n      step116 <- step115 + stepTotal\n      step117 <- step116 + stepTotal\n      step118 <- step117 + stepTotal\n      step119 <- step118 + stepTotal\n      step120 <- step119 + stepTotal\n      step121 <- step120 + stepTotal\n      step122 <- step121 + stepTotal\n      step123 <- step122 + stepTotal\n      step124 <- step123 + stepTotal\n      step125 <- step124 + stepTotal\n      step126 <- step125 + stepTotal\n      step127 <- step126 + stepTotal\n      step128 <- step127 + stepTotal\n      step129 <- step128 + stepTotal\n      step130 <- step129 + stepTotal\n      step131 <- step130 + stepTotal\n      step132 <- step131 + stepTotal\n      step133 <- step132 + stepTotal\n      step134 <- step133 + stepTotal\n      step135 <- step134 + stepTotal\n      step136 <- step135 + stepTotal\n      step137 <- step136 + stepTotal\n      step138 <- step137 + stepTotal\n      step139 <- step138 + stepTotal\n      step140 <- step139 + stepTotal\n      step141 <- step140 + stepTotal\n      step142 <- step141 + stepTotal\n      step143 <- step142 + stepTotal\n      step144 <- step143 + stepTotal\n      step145 <- step144 + stepTotal\n      step146 <- step145 + stepTotal\n      step147 <- step146 + stepTotal\n      step148 <- step147 + stepTotal\n      step149 <- step148 + stepTotal\n      step150 <- step149 + stepTotal\n      step151 <- step150 + stepTotal\n      rtn: formatReport(step151, averageValue(values, stepTotal))","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 at least 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":"#!ek9\ndefines 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, every captured variable represents a deliberate dependency that binds the current object into the closure (the object is shared with the enclosing scope, not copied: a later in-place mutation is visible inside, while reassigning the outer variable with := is not). If a captured variable is never referenced inside the function body, that capture is dead weight: it adds an unnecessary dependency 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\nIf you DID use the capture in the body and still see E11018, check its name: a named capture called like a function or type (e.g. '(helper: 7)' beside a function helper()) is rejected with E01030 at the capture, because inside the body the name would have two meanings - rename the capture (see Q1392).\n\nSee Q53 for closure capture mechanics. See Q319 for unused closure capture detection. See Q311 for quality checks.","ek9Example":"#!ek9\ndefines 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 'stdout', but the body only reads 'factor', 'x' and 'y'. An explicitly captured variable that the function body never references is dead weight - an unnecessary and misleading 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 highly complex AND large have the lowest reliability (Rosenberg, Hammer and Shaw, ISSRE 1998).\n\nSee Q311 for the full list of quality checks and Q312 for how complexity is measured.","ek9Example":"#!ek9\ndefines 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 Q862 for the per-component injection-field limit (E11040). See Q311 for quality checks.","ek9Example":"#!ek9\ndefines 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}`)\n\n      cancel()\n        -> label as String\n        audit.record(`cancel ${label}`)\n\n      refund()\n        -> label as String\n        audit.record(`refund ${label}`)","incorrect":"      submit()\n        -> label as String\n        audit as AuditService!\n        audit.record(`submit ${label}`)\n\n      cancel()\n        -> label as String\n        audit as AuditService!\n        audit.record(`cancel ${label}`)\n\n      refund()\n        -> label as String\n        audit as AuditService!\n        audit.record(`refund ${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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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 'items' is a List of String 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 Q1286 for the private no-arg escape hatch.","ek9Example":"#!ek9\ndefines 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?","assign-if-unset","constructor","default","field","generic","null","optional","present-but-unset","uninitialised"],"primaryTopics":[],"typicalErrors":[{"error":"E08060","correct":"Container() as pure\n        item :=? T()","incorrect":"default Container() as pure","explanation":"A generic 'default Container()' leaves the 'T?' field uninitialised, so reading it in hasItem() is E08060 (the compiler also reports E07175: a default constructor must be private when properties are uninitialised). Give the field an unset T() in the no-arg constructor (item :=? T()), or declare the default private. See ek9 -h E08060 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 Q1286 for the constructor access rule.","ek9Example":"#!ek9\ndefines 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":"E08060","correct":"default private Holder() as pure","incorrect":"default Holder() as pure","explanation":"A public 'default' no-arg constructor leaves the uninitialised 'T?' field 'item' never set, so flow analysis reports E08060 on the field, and E07175 follows on the class: a generic with an uninitialised 'T?' field must keep its no-arg default constructor 'private'. A public one 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 E08060 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\nConstructing one:\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\nConstructing one:\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":"#!ek9\ndefines 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":"E06180","correct":"Required(\"must have\")","incorrect":"Required() of String","explanation":"Shape 2 makes the no-argument constructor private so no empty instance can exist. Asking for one with Required() from outside the type is not accessible, triggering E06180; callers must pass a value. See ek9 -h E06180 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, or stores it into another object, 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)`, or storing it in a collection owned by someone else, lets the not-yet-finished object be used. EK9 rejects `this` as an argument inside a constructor body, and also a local that holds `this` (`other <- this` then `reg.register(other)` is E08250 on `other`), and so does an own property the constructor stored `this` into (`me: this` then `reg.register(me)`). So is a dynamic function or dynamic class that captures `this` (`(me: this) is Callback`) when it is passed out, directly or through a local: the captured object is just as half-built. Calling such a dynamic function inside the constructor is allowed.\n\nSTORING IS THE SAME ESCAPE\nStoring any of these into ANOTHER object is E08250 too, because the other object can call back into the half-built one: a property of another instance (`p.parent: this`), a collection another object holds or returns (`p.kids += this`, `reg.members() += this`), another object's mutating operator (`reg += this`), or a collection passed in (`others += this`). Storing `this` in the object's OWN property is a self-reference and is allowed (`kids += this`, `this.kids += this`); a chain through an own property (`this.me.parent: this`) reaches another object and is not.\n\nHOW TO FIX\nComplete construction first, then register or link (`child <- Child(); parent.adopt(child)`). 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 rejects it at compile time, so `this` cannot be passed out of a constructor or stored into another object, directly or through a local that holds it.","ek9Example":"#!ek9\ndefines 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: passing `this` (or a local holding `this`, or a dynamic function/class capturing it) as an argument, or storing it into another object's property or collection, inside a constructor body is a compile-time error (E08250); storing it in the object's own property is allowed.","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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    default operator ?\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\nDOWN A CHAIN\nThe rule applies at every level, whatever order the types are declared in: a Middle that extends such a base gets no generated no-argument constructor, so a Leaf that extends Middle gets none either (E07177 if it declares no constructor, E07176 for 'default Leaf()'), and calls Middle's constructor with super(...). A type with no no-argument constructor cannot be built with 'Leaf()', nor used to parameterise a generic that calls 'T()'.\n\nSee Q886 for E07175 (private default). See Q581 for super() delegation. See Q1346 for the 'T?' null-safety idiom.","ek9Example":"#!ek9\ndefines 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":"//no constructor declared","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 the :=? guard and a re-pointing ':=' work in a while expression?","while loop as an expression with a guard operator","How do I combine an assign-if-unset guard 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 THAT RE-POINTS AND CHECKS\nA ':=' pre-flow assigns the right-hand side to an existing variable (an unset value included) and checks nothing, so the check goes in the control: 'then reading? and looping'. If reading is unset, the loop body never runs and the expression yields the returning variable's initial value:\n  reading as Integer?\n  looping <- true\n  total <- while reading := nextReading() then reading? and 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 pre-flow runs once at entry. A '<-' or ':=?' guard that leaves its variable unset skips the body; after a ':=' the control's own 'x?' does that.\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 guard operators.","ek9Example":"#!ek9\ndefines 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 that re-points 'reading' and checks it in the control\n      reading as Integer?\n      looping <- true\n      total <- while reading := nextReading() then reading? and 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 (<-, :=?) or an explicit 'x := f() then x?' in one header, with the returning variable guaranteeing a defined result even when the guard is unset.","keywords":[":=?","assignment","control","expression","flow","guard","isset","loop","returning","then","value","while"],"primaryTopics":["while expression","guard variable","assign-if-unset"],"typicalErrors":[{"error":"E08050","correct":"        <- rtn as Integer: 0\n        rtn: rtn + reading","incorrect":"        <- rtn as Integer?\n        rtn: reading","explanation":"A body-skipping guard ('<-', ':=?') or a failed 'x?' control can skip the while body entirely, and on that path the result is the returning variable's INITIAL value. Declared uninitialised ('<- rtn as Integer?') there would be nothing to return, so the compiler rejects it. Give the return an initial value. See ek9 -h E08050 for details."}],"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","assign-if-unset 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  more <- true\n  do first <- fetchRecord()\n    stdout.println(`got ${first}`)\n    more: false\n  while more\nThe declaration guard (<-) declares 'first' for the loop, assigns fetchRecord() to it and checks it is SET before the body runs. To re-point an EXISTING variable and skip the loop when the new value is unset, assign it first and wrap the loop: 'first := fetchRecord()' then 'if first?' around the do/while.\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":"#!ek9\ndefines 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 <- declaration guard (guard checked once, after 'do')\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","isset","loop","returning","while"],"primaryTopics":["do-while loop","guard variable","assign-if-unset"],"typicalErrors":[{"error":"E08050","correct":"        <- rtn as String: \"none\"","incorrect":"        <- rtn as String?","explanation":"A body-skipping guard ('<-', ':=?') can skip the do/while body entirely, and on that path the result is the returning variable's INITIAL value. Declared uninitialised ('<- rtn as String?') there would be nothing to return, so the compiler rejects it. Give the return an initial value. See ek9 -h E08050 for details."}],"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 <- declaration guard","assign-if-unset 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  total <- for step <- getStep() with i in 1 ... 5\n    <- rtn as Integer: 0\n    rtn: rtn + (i * step)\nIf getStep() is unset the guard 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":"#!ek9\ndefines 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 <- declaration guard (guard FIRST, then 'with i in range')\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","isset","loop","range","returning","with"],"primaryTopics":["for range loop","guard variable","for expression"],"typicalErrors":[{"error":"E08050","correct":"<- rtn as Integer: 0\n        rtn: rtn + (i * step)","incorrect":"<- rtn as Integer?\n        rtn: i * step","explanation":"A body-skipping guard ('<-', ':=?') can skip the for-range body entirely, and on that path the result is the returning variable's INITIAL value. Declared uninitialised ('<- rtn as Integer?') there would be nothing to return, so the compiler rejects it (E08050). Give the return an initial value. See ek9 -h E08050 for details."}],"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 <- declaration guard","assign-if-unset 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  joined <- for prefix <- getPrefix() with item in items\n    <- rtn as String: \"\"\n    rtn: `${rtn}${prefix}${item}`\nThe declaration guard (<-) declares 'prefix' for the loop, assigns getPrefix() to it 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":"#!ek9\ndefines 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 <- declaration guard (guard FIRST, then 'with item in collection')\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","isset","iterate","returning","with"],"primaryTopics":["for in loop","guard variable","for expression"],"typicalErrors":[{"error":"E08050","correct":"        <- rtn as String: \"\"\n        rtn: `${rtn}${prefix}${item}`","incorrect":"        <- rtn as String?\n        rtn: `${prefix}${item}`","explanation":"A body-skipping guard ('<-', ':=?') can skip the for-in body entirely, and on that path the result is the returning variable's INITIAL value. Declared uninitialised ('<- rtn as String?') there would be nothing to return, so the compiler rejects it. Give the return an initial value. See ek9 -h E08050 for details."}],"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 guard in its header?","try as an expression with a guard variable","assign-if-unset 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  chosen <- try config <- loadConfig()\n    <- rtn as String: \"default.yaml\"\n    rtn: config\n  catch\n    -> ex as Exception\n    rtn: `error ${ex}`\nThe declaration guard (<-) declares 'config' for the try, assigns loadConfig() to it 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. When the guard leaves its variable unset the WHOLE try is skipped - body, catch AND finally - so a finally that must always run belongs outside a guarded try.\n\nSee Q78 for try guards. See Q1157 for try guards. See Q137 for try-with-resources. See Q1361 for guards across all expression-form constructs.","ek9Example":"#!ek9\ndefines 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 <- declaration guard (guard right after 'try')\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(problem.reason())","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","isset","returning","try"],"primaryTopics":["try expression","guard variable","assign-if-unset"],"typicalErrors":[{"error":"E08050","correct":"        <- rtn as String: \"default.yaml\"","incorrect":"        <- rtn as String?","explanation":"A body-skipping guard ('<-', ':=?') can skip the try body entirely, and on that path the result is the returning variable's INITIAL value. Declared uninitialised ('<- rtn as String?') there would be nothing to return, so the compiler rejects it. Give the return an initial value. See ek9 -h E08050 for details."}],"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 an assign-if-unset","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 ':=' (always assigns and checks nothing - add 'if x?' around the switch to skip it when the new value is unset) 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":"#!ek9\ndefines 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","assign-if-unset","guard variable"],"typicalErrors":[{"error":"E08050","correct":"        <- rtn as Integer: 0","incorrect":"        <- rtn as Integer?","explanation":"A body-skipping guard ('<-', ':=?') can skip the switch body entirely, and on that path the result is the returning variable's INITIAL value. Declared uninitialised ('<- rtn as Integer?') there would be nothing to return, so the compiler rejects it. Give the return an initial value. See ek9 -h E08050 for details."}],"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: '?:' (coalesce, Elvis: '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":"#!ek9\ndefines 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":[],"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 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 operators, the same skip-on-unset semantics, the same 'returning variable must be initialised' rule — everywhere.\n\nTHE SAME :=? GUARD, SIX CONSTRUCTS (g starts uninitialised; 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 guard operators. See Q1360 for the ternary (the if-expression equivalent).","ek9Example":"#!ek9\ndefines 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      // Every guard variable starts uninitialised, so ':=?' assigns source(); it is set, so every body runs\n      // 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 and 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":"whileValue <- while whileGuard :=? source() then loopFlag\n        <- rtn as Integer: -1","incorrect":"whileValue <- while whileGuard :=? source() then loopFlag\n        <- rtn as Integer?","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":"#!ek9\ndefines 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 assign-if-unset 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 / ASSIGN-IF-UNSET 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  defines class\n    Basket\n      items <- List() of String\n\n      items()\n        <- rtn as List of String: items\n\n      default operator ?\n\n  defines program\n    Demo()\n      basket <- Basket()\n      basket.items() := List() of String    // 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":"#!ek9\ndefines 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.\nMerge (:~:) does not stack: it never overwrites a set value and fills whatever the target lacks, element by element - a wholly unset CSV takes the source; otherwise a missing header, rows past the target's last row, and cells that are missing or empty are filled. See Q1407 for merging partial CSVs.\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":"#!ek9\ndefines 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()","incorrect":"if result.isError()","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":"#!ek9\ndefines 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":"defines program\n\n    SetUnique()","incorrect":"defines class\n    MyThing extends Set of String\n\n  defines program\n\n    SetUnique()","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":"#!ek9\ndefines 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":"#!ek9\ndefines 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":"#!ek9\ndefines 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: sin, cos, tan, asin, acos, atan, atan2. The logarithmic family is log (natural), log10 and exp.\nAlso 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  pi <- 3.14159\n  rounded <- pi.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":"#!ek9\ndefines 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":"#!ek9\ndefines module qa.security.sanitizedread\n\n  defines program\n\n    SanitizedVsUnsanitizedRead()\n      stdout <- Stdout()\n\n      // === THE SAMPLE FILE: written here so the example is self-contained ===\n\n      path <- FileSystemPath(\"/tmp/qa-example-mixed.txt\")\n      made <- path.createFileIfAbsent()\n      if made.isError()\n        stdout.println(\"Could not create the sample file: \" + made.error())\n\n      file <- TextFile(path)\n      stdout.println(\"File: \" + $file)\n\n      try\n        -> output <- file.output()\n        output.println(\"hello world\")\n        output.println(\"'; DROP TABLE users--\")\n        output.println(\"\")\n        output.println(\"<script>alert('xss')</script>\")\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      // A blank line is a SET (empty) String, so it is never confused with a rejection.\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()","incorrect":"line <- input.nextLine()","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":[]}
{"id":1371,"category":"Data Flow Safety","question":"What is EK9's Symbolic (Contract) analysis and how do I read the Flow pane's Symbolic mode?","url":"https://ek9.io/qa/QA1371.html","alternatePhrasings":["What does the Flow Analysis 'Symbolic analysis' toggle show?","What is a contract counterexample vs a proved-safe result?","How does EK9 find divide-by-zero boundaries?","What is the S badge in the editor gutter?","What does ek9_verify_method do?","How do I fix a divide by zero the scanner found?"],"answer":"SYMBOLIC (CONTRACT) ANALYSIS\nEK9's Symbolic/Contract engine is a bounded-model checker built into the compiler. For a function it finds a CONCRETE input that violates a contract - or proves that none exists within the bounded model. It answers 'can this break?', not merely 'is it broken right now?'.\n\nTWO VERDICTS PER OBLIGATION\n  COUNTEREXAMPLE (must-fix): a witnessing input that panics or is silently unset. Example: 'total / quantity' is refuted by quantity=0.\n  PROVED-SAFE (green): the obligation is discharged - it cannot fail. A division is proved-safe when the divisor is a non-zero literal, is dominated by a 'require x <> 0' (or an enclosing 'if x <> 0' guard), or has a constrained TYPE whose window excludes 0 (e.g. 'Quantity as Integer constrain as > 0').\n\nOBLIGATION KINDS\n  - DIV_BY_ZERO: a division/mod/rem by a value that can be zero.\n  - CALLEE_REQUIRE: a call whose argument breaks the callee's 'require' precondition.\n  - CONSTRAINED_CONSTRUCTION: a constrained-type construction whose value can leave the type's window.\n\nWHERE TO SEE IT\n  IDE Flow Analysis pane -> tick 'Symbolic analysis' (default off). You get a summary strip (N must-fix, N advisory, N proved-safe), a Contract-findings list with the witness and a paste-ready @Test, and a detail drawer. Each obligation line also gets an 'S' badge in the editor gutter (hover it for the verdict) - scoped to the function you are analysing.\n  '+ Add as @Test' generates a verify-directed @Test that pins the boundary (same engine as ek9_generate_edge_case_tests).\n  AI / CLI: the ek9_verify_method tool returns the verdict for a function.\n  Command line: the SYMBOLIC_ANALYSIS phase is not reachable from the ek9 command line yet (there is no -Xverify option); use the Flow Analysis toggle or the ek9_verify_method tool.\n\nHOW TO FIX A COUNTEREXAMPLE\n  1. Guard it with a precondition: add 'require quantity <> 0'. IMPORTANT - a return's inline initialiser runs BEFORE any require, so to guard the computation declare the return uninitialised ('rtn as Integer?') and assign it AFTER the require (see safeUnitPrice below). Guard first, compute second.\n  2. Lift the value to a constrained type ('Quantity as Integer constrain as > 0'). The invariant then lives on the TYPE: you cannot construct a Quantity of 0, so every function taking a Quantity is safe by construction and no require is needed. The scanner reads the constraint window off the type and proves the division safe.\n\nThe scanner is high-precision by design: it discharges literal / guarded / type-safe divisions and only cries wolf on a genuine free-parameter boundary.\n\nSee Q248 for error-code lookup, Q311 for the quality checks catalog.","ek9Example":"#!ek9\ndefines module qa.dataflowsafety.symbolicanalysis\n\n  defines function\n\n    //COUNTEREXAMPLE quantity=0: dividing by a free parameter is a silent unset - the Symbolic engine\n    //finds the witnessing input and offers a directed @Test in the Flow pane.\n    unitPrice()\n      ->\n        total as Integer\n        quantity as Integer\n      <- rtn as Integer: total / quantity\n\n    //PROVED-SAFE: the require dominates the division. The return is declared uninitialised ('as Integer?')\n    //and assigned AFTER the require, so the guard runs before the computation (guard first, compute second).\n    safeUnitPrice()\n      ->\n        total as Integer\n        quantity as Integer\n      <- rtn as Integer?\n      require quantity <> 0\n      rtn: total / quantity","migrationContext":"Dafny / Frama-C / SMT-backed verifiers do heavier proofs but are separate, opt-in tools with their own annotation languages. Java / Python / Go / C++ have no built-in contract counterexample search. EK9 bakes a bounded contract-checker into the compiler and IDE, returns a CONCRETE witnessing input (not just 'unsafe'), and turns each witness into a directed @Test - one engine, surfaced in the Flow pane and the ek9_verify_method tool.","keywords":["S badge","analysis","boundary","constrained","contract","counterexample","divide","ek9_verify_method","flow","guard","obligation","panic","precondition","proved","require","safe","symbolic","unitPrice","verify","witness","zero"],"primaryTopics":["symbolic analysis","contract counterexample"],"typicalErrors":[],"companions":[]}
{"id":1372,"category":"Generics","question":"How do I run a callback over a collection that is held inside an Optional or Result?","url":"https://ek9.io/qa/QA1372.html","alternatePhrasings":["whenPresent over an Optional of a List","whenOk / whenError with a List value in a Result","Pass a Consumer to a container whose element is itself a generic type","Callback receives a List of String from an Optional"],"answer":"A built-in generic container (Optional, Result) can be parameterised over another generic type - Optional of (List of String), Result of (List of String, Integer) - and its callback methods hand the FULLY-TYPED value to your function, exactly as the return methods (get/ok) do. You write the callback the same way you would over a simple element.\n\nOPTIONAL OF A LIST - whenPresent\n  items <- List() of String\n  items += \"alpha\"\n  opt <- Optional(items)\n\n  printer <- (stdout) is Acceptor of List of String as function\n    stdout.println(`present: ${$t}`)\n  opt.whenPresent(printer)          // printer receives a List of String\n\nRESULT OF A LIST - whenOk / whenError\n  listResult <- Result(items, 0)\n  onOk <- (stdout) is Acceptor of List of String as function\n    stdout.println(`ok: ${$t}`)\n  listResult.whenOk(onOk)\n\nThe callback parameter is the SAME type you parameterised the container with. Whether the element is a simple type (String) or itself a generic (List of String, Dict of (String, Integer)), the value you receive is the typed wrapper - never a raw internal delegate.\n\nGUARD BEFORE get()/ok()\nThe callback forms (whenPresent/whenOk/whenError) fire only in the right state, so they need no guard. The value-returning forms do - guard first:\n\n  if opt?\n    got <- opt.get()\n\nUse the callback form when you just want to act on the value, the get form when you need it in the surrounding scope.\n\nSee Q47 for Optional. See Q48 for Result. See Q707 for Consumer/Acceptor. See Q198 for the built-in generics.","ek9Example":"#!ek9\ndefines module qa.genericsdeep.collectioncallback\n\n  defines program\n\n    CollectionCallbackDemo()\n      stdout <- Stdout()\n\n      items <- List() of String\n      items += \"alpha\"\n      items += \"beta\"\n\n      // Optional of (List of String) - whenPresent hands the typed List to the callback\n      opt <- Optional(items)\n      printer <- (stdout) is Acceptor of List of String as function\n        stdout.println(`present: ${$t}`)\n      opt.whenPresent(printer)\n\n      // Result of (List of String, Integer) - whenOk hands the typed List to the callback\n      listResult <- Result(items, 0)\n      onOk <- (stdout) is Acceptor of List of String as function\n        stdout.println(`ok: ${$t}`)\n      listResult.whenOk(onOk)\n\n      // Value-returning form needs a guard first\n      if opt?\n        got <- opt.get()\n        stdout.println(`got: ${$got}`)","migrationContext":"Java: optional.ifPresent(list -> ...) where list is List<String>; the type is preserved. EK9: opt.whenPresent(acceptor) where the Acceptor is 'of List of String' - identical mental model, and the compiler guarantees the callback receives the typed value even when the element is itself a parameterised type.","keywords":["Acceptor","Consumer","List","Optional","Result","callback","collection","container","generic","parameterised","whenError","whenOk","whenPresent"],"primaryTopics":["callbacks over containers","Optional of List","Result of List"],"typicalErrors":[{"error":"E08030","correct":"if opt?\n        got <- opt.get()\n        stdout.println(`got: ${$got}`)","incorrect":"got <- opt.get()\n      stdout.println(`got: ${$got}`)","explanation":"Optional.get() must be guarded by a set-check first (E08030). The whenPresent callback form needs no guard because it only fires when a value is present. See ek9 -h E08030 for details."}],"companions":[]}
{"id":1373,"category":"Generics","question":"Do built-in generic types and my own generic types behave the same when I pass a callback over a collection?","url":"https://ek9.io/qa/QA1373.html","alternatePhrasings":["Is Optional of List of String the same to use as my own Holder of List of String?","Scenario-2 built-in generic versus Scenario-3 user generic surface parity","Will my generic class behave like the built-in ones for whenPresent-style callbacks?","Do user-defined generics and library generics feel the same?"],"answer":"Yes. From the point of view of the code that USES them, a built-in generic (List, Optional, Result - implemented in the runtime) and a generic you define yourself behave identically, including when the element is itself a parameterised type such as List of String. You call them the same way and the callback receives the same typed value. The backends implement them differently, but that difference never leaks to you.\n\nYOUR OWN GENERIC (needs two constructors - see Q643)\n  Holder of type T\n    held as T?\n    Holder() as pure\n      held :=? T()\n    Holder() as pure\n      -> initial as T\n      held :=? initial\n    whenPresent()\n      -> consumer as Consumer of T\n      if held?\n        consumer(held)\n    default operator ?\n\nUSING BOTH THE SAME WAY\n  words <- List() of String\n  words += \"widget\"\n\n  h <- Holder(words)                                 // user generic\n  hc <- (stdout) is Consumer of List of String as pure function\n    stdout.println(`user-generic: ${$t}`)\n  h.whenPresent(hc)\n\n  opt <- Optional(words)                             // built-in generic\n  oc <- (stdout) is Consumer of List of String as pure function\n    stdout.println(`built-in: ${$t}`)\n  opt.whenPresent(oc)\n\nBoth print the same list. The callback is 'Consumer of List of String' in both cases; both hand you the typed List. This surface parity is the guarantee: pick a built-in generic or write your own, and the calling code, the callback shapes, and the tri-state behaviour are the same.\n\nSee Q643 for the two-constructor rule. See Q198 for built-in generics. See Q1372 for callbacks over a container's collection.","ek9Example":"#!ek9\ndefines module qa.genericsdeep.callbackparity\n\n  defines class\n\n    Holder of type T\n      held as T?\n\n      Holder() as pure\n        held :=? T()\n\n      Holder() as pure\n        -> initial as T\n        held :=? initial\n\n      whenPresent()\n        -> consumer as Consumer of T\n        if held?\n          consumer(held)\n\n      default operator ?\n\n  defines program\n\n    CallbackParityDemo()\n      stdout <- Stdout()\n\n      words <- List() of String\n      words += \"widget\"\n\n      // User-defined generic (Scenario 3 - monomorphised directly)\n      h <- Holder(words)\n      hc <- (stdout) is Consumer of List of String as pure function\n        stdout.println(`user-generic: ${$t}`)\n      h.whenPresent(hc)\n\n      // Built-in generic (Scenario 2 - delegating wrapper) - same call, same result\n      opt <- Optional(words)\n      oc <- (stdout) is Consumer of List of String as pure function\n        stdout.println(`built-in: ${$t}`)\n      opt.whenPresent(oc)","migrationContext":"Java: a library Optional<T> and a user-defined generic Box<T> both use the same call syntax; type erasure means neither preserves T at runtime. EK9: user and built-in generics share the same call syntax AND the compiler preserves the parameterised element type through callbacks for both - the built-in (delegating) and user (monomorphised) implementations are indistinguishable to the caller.","keywords":["Consumer","Holder","Optional","built-in","callback","generic","monomorphization","parity","scenario2","scenario3","surface","user-defined","whenPresent"],"primaryTopics":["generic surface parity","user vs built-in generics"],"typicalErrors":[{"error":"E06040","correct":"Holder() as pure\n        held :=? T()\n\n      Holder() as pure\n        -> initial as T\n        held :=? initial","incorrect":"Holder() as pure\n        -> initial as T\n        held :=? initial","explanation":"A generic class needs both a public no-arg constructor and the value constructor (E06040 / E06060). Provide both so the type can be default-constructed and value-constructed. See ek9 -h E06040 for details."}],"companions":[]}
{"id":1374,"category":"Generics","question":"Can I store my own generic type inside a built-in container like List, Optional, Result or Dict?","url":"https://ek9.io/qa/QA1374.html","alternatePhrasings":["Put a user-defined generic as the element of a List or Optional","Use my own Holder of T as a Dict value or Result ok type","Nest a user generic inside a built-in generic","Optional of (my generic type)"],"answer":"Yes. A user-defined generic (your own {@code Holder of T}, {@code Pair of (A, B)}, ...) can be the element, value, key, ok or error type of ANY built-in container - List, Set, Optional, Result, Dict, PriorityQueue, MutexLock - and it behaves exactly like a built-in element. You construct, store and retrieve it the same way; the compiler carries the parameterised element type through for you. The built-in container (delegating wrapper) and your user generic (monomorphised) are implemented differently, but that never leaks to your code.\n\nUSER GENERIC AS A LIST ELEMENT\n  holders <- List() of (Holder of String)\n  holders += Holder(\"alpha\")\n  for h in holders\n    stdout.println(h.get())          // alpha\n\nAS AN OPTIONAL VALUE\n  maybe <- Optional(Holder(42))\n  if maybe?\n    stdout.println(`held: ${maybe.get().get()}`)   // held: 42\n\nAS A DICT VALUE\n  scores <- Dict() of (String, Holder of Integer)\n  scores += DictEntry(\"a\", Holder(1))\n\nAS A RESULT OK TYPE\n  outcome <- Result(Holder(\"ok\"), 0)\n  outcome.whenOk(consumer)           // consumer receives a Holder of String\n\nNESTING BOTH WAYS ALSO WORKS\n  wrapped <- Optional() of (Holder of (List of String))   // user generic wrapping a built-in generic\n  nested <- Optional() of (Holder of (Holder of String))  // user generic wrapping a user generic\n\nWHAT YOUR GENERIC NEEDS\nTwo constructors (Q643): a public no-arg and a value constructor. If you use it as a Dict KEY, a Set element, or a PriorityQueue element, default the operators those containers need. A type defines sameness ONCE: choose {@code default operator <=>} if it must be ordered (a PriorityQueue element), otherwise {@code default operator ==}; the other comparisons are generated from it, and requesting both '==' and '<=>' is E07181. Add {@code default operator #?} when it is a Dict key or Set element.\n\nSee Q198 for built-in generics. See Q643 for the two-constructor rule. See Q1373 for built-in vs user generic surface parity.","ek9Example":"#!ek9\ndefines module qa.genericsdeep.usergenincontainer\n\n  defines class\n\n    Holder of type T\n      held as T?\n\n      Holder() as pure\n        held :=? T()\n\n      Holder() as pure\n        -> initial as T\n        held :=? initial\n\n      get() as pure\n        <- rtn as T?\n        rtn: held\n\n      default operator ?\n\n  defines program\n\n    UserGenericInContainerDemo()\n      stdout <- Stdout()\n\n      // User generic as a List element\n      holders <- List() of (Holder of String)\n      holders += Holder(\"alpha\")\n      holders += Holder(\"beta\")\n      for h in holders\n        stdout.println(`list: ${h.get()}`)\n\n      // User generic as an Optional value\n      maybe <- Optional(Holder(42))\n      if maybe?\n        stdout.println(`optional: ${maybe.get().get()}`)\n\n      // User generic as a Dict value\n      scores <- Dict() of (String, Holder of Integer)\n      scores += DictEntry(\"a\", Holder(1))\n      for entry in scores\n        stdout.println(`dict: ${entry.value().get()}`)\n\n      // User generic as a Result ok type\n      outcome <- Result(Holder(\"ok\"), 0)\n      reporter <- (stdout) is Consumer of (Holder of String) as pure function\n        stdout.println(`result: ${t.get()}`)\n      outcome.whenOk(reporter)","migrationContext":"Java/Kotlin: List<MyGeneric<String>> is routine; type erasure means the element type is not preserved at runtime. EK9: a built-in container over a user generic is equally routine AND the parameterised element type is preserved through storage and retrieval - your Holder of String comes back as a Holder of String, not an erased Object.","keywords":["Dict","Holder","List","Optional","Pair","Result","built-in","container","element","generic","key","monomorphization","nest","scenario3","user","value"],"primaryTopics":["user generic in built-in container","nesting generics"],"typicalErrors":[{"error":"E06040","correct":"Holder() as pure\n        held :=? T()\n\n      Holder() as pure\n        -> initial as T\n        held :=? initial","incorrect":"Holder() as pure\n        -> initial as T\n        held :=? initial","explanation":"A generic class used inside a container still needs both a public no-arg and a value constructor (E06040 / E06060). Provide both. See ek9 -h E06040 for details."}],"companions":[]}
{"id":1375,"category":"Web Services","question":"Why does EK9 complain that my application registers services but the program creates no HTTP?","url":"https://ek9.io/qa/QA1375.html","alternatePhrasings":["What is E12020 in EK9?","I registered a service but never see it respond — why?","Can I use an application that registers services from a non-serving program?","Why must a program with a service application create HTTP?","How do I share components with a program that does not serve HTTP?"],"answer":"A registered service is only reachable while something serves it. If the program linked to an application never creates an HTTP instance, the program runs its body and EXITS — the service is constructed, consumes its dependencies, and can never receive a request. Nothing at runtime reports this: the process exits successfully, so the mistake stays invisible until someone notices the endpoint was never live. EK9 raises E12020 at compile time instead.\n\nTWO DIRECTIONS OF ONE MISTAKE\nE12020 is the mirror of E12021. E12021 fires when a program creates HTTP but its application registers no services (a server with nothing to route to). E12020 fires when the application registers services but the program creates no HTTP (services with nothing serving them). Both are decidable at compile time because the application's registrations and the program's variable declarations are already resolved.\n\nTHE TWO FIXES\nEither serve them — create NetworkProperties, an HTTP instance, and call serve() — or SPLIT the application. Splitting is the right answer when the program genuinely needs only the components: give it an application registering just those, and keep the service registration in the application used by the serving program. Conflating serving wiring with component wiring is exactly what leaves a REST service registered and permanently unreachable.","ek9Example":"#!ek9\ndefines module qa.webservices.serviceswithouthttp\n\n  defines constant\n\n    plainText <- \"text/plain\"\n\n  defines service\n\n    GreetService for :/greet open\n\n      hello() as GET for :/hello\n        <- response as HTTPResponse: () with trait of HTTPResponse\n          override status() as pure\n            <- rtn as Integer: 200\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(\"hello\")\n          override contentType() as pure\n            <- rtn as String: plainText\n          default operator ?\n\n  defines component\n\n    Repository as abstract\n      describe() as pure abstract\n        <- rtn as String?\n\n    InMemoryRepository extends Repository\n      override describe() as pure\n        <- rtn as String: \"in-memory\"\n\n  defines application\n\n    // Serving wiring: registers the service, so its program MUST create HTTP.\n    GreetApp\n      register InMemoryRepository() as Repository\n      register GreetService()\n\n    // Component wiring only: safe for a program that does not serve.\n    RepositoryOnly\n      register InMemoryRepository() as Repository\n\n  defines program\n\n    ServeIt() with application of GreetApp\n      stdout <- Stdout()\n      props <- NetworkProperties(19099)\n      stdout.println(\"serving\")\n      HTTP(props).serve()\n\n    ReportOnly() with application of RepositoryOnly\n      repository as Repository!\n      stdout <- Stdout()\n      stdout.println(repository.describe())","migrationContext":"Spring Boot: @RestController beans are picked up by the embedded server automatically, so 'registered but never served' cannot easily arise — the framework always starts a server. JAX-RS/Jersey: resources are registered in an Application subclass and served by the container; an unserved resource is a deployment error found at runtime. Node/Express: routes are attached to an app that must still call listen() — forgetting listen() is the same bug, but it is only visible when the process exits. EK9: the compiler rejects it, so the process can never be built in that state.","keywords":["E12020","E12021","application","component","http","injection","register","rest","serve","service","web","wiring"],"primaryTopics":["web-services","application-wiring"],"typicalErrors":[{"error":"E12020","correct":"ReportOnly() with application of RepositoryOnly","incorrect":"ReportOnly() with application of GreetApp","explanation":"GreetApp registers GreetService, so a program wired to it must create and serve an HTTP instance. Without one the program prints and exits and the service is never reachable. See ek9 -h E12020 for details."},{"error":"E12020","correct":"RepositoryOnly\n      register InMemoryRepository() as Repository","incorrect":"RepositoryOnly\n      register InMemoryRepository() as Repository\n      register GreetService()","explanation":"When a program needs only the components, split the application: keep the service registration in the application the serving program uses, and give the non-serving program one that registers only what it needs. See ek9 -h E12020 for details."}],"companions":[]}
{"id":1376,"category":"Advanced Type System","question":"Why does my constrained type fail to compile with 'no value can satisfy this constraint'?","url":"https://ek9.io/qa/QA1376.html","alternatePhrasings":["I get E08261 CONSTRAINT_ADMITS_NO_VALUE on my constrained type declaration","My constrain as clause is rejected as impossible - what is wrong with it?","Write a constrained Integer range that actually admits values","Why is '> 0 and < 1' rejected on an Integer but allowed on a Float?","The compiler says my constrained type admits no value, how do I fix the bounds?"],"answer":"The constraint you wrote cannot be satisfied by ANY value of the base type, so every construction of it would Panic and no variable of it could ever hold a value. EK9 proves this at the declaration (E08261) rather than leaving it to fail at runtime.\n\nThe usual cause is strict operators on a WHOLE-NUMBER base:\n\n  defines type\n    Wrong as Integer constrain as\n      > 0 and < 1          //E08261 - nothing lies between 0 and 1\n\n    Right as Integer constrain as\n      >= 0 and <= 1        //admits 0 and 1\n\nDiscreteness matters: '> 0.0 and < 1.0' over Float is perfectly fine because 0.5 satisfies it. The same shape is empty over Integer and populated over Float.\n\nThe other causes are bounds written in the wrong order ('>= 10 and <= 5'), 'and' used where 'or' was meant, and an '==' beside a clause that contradicts it ('== 5 and > 10' - the '==' pins the only candidate there is, so any other clause can only take it away).\n\nOnly PROVABLE cases are reported. An 'or' anywhere, a regex, or a base whose ordering is not modelled is left to the runtime check.\n\nSee Q257 for constrained types. See Q1185 for a Percentage range and E08260.","ek9Example":"#!ek9\ndefines module qa.advancedtypes.unsatisfiableconstraint\n\n  defines type\n\n    //Inclusive bounds - admits 0 through 9. The strict form '> 0 and < 1' would be E08261.\n    Digit as Integer constrain as\n      >= 0 and <= 9\n\n    //Bounds may meet at a single value: this admits exactly 5, and is NOT empty.\n    ExactlyFive as Integer constrain as\n      >= 5 and <= 5\n\n    //🔑 Discreteness matters. This is the SAME shape that is impossible over Integer, and it is\n    //perfectly satisfiable over Float because values lie between the bounds.\n    Fraction as Float constrain as\n      > 0.0 and < 1.0\n\n    //Values on either side of a gap need 'or' - an 'and' here would admit nothing.\n    Outlier as Integer constrain as\n      <= 10 or >= 90\n\n    //A lone '==' is fine; it is only contradicted when another clause excludes the pinned value.\n    Answer as Integer constrain as\n      == 42\n\n    //Suffixed bounds must share ONE suffix. A Money varies along TWO axes - magnitude AND currency -\n    //and only the magnitude may move, so bounds in different currencies admit nothing.\n    Budget as Money constrain as\n      > 330#GBP and < 450#GBP\n\n  defines program\n\n    UnsatisfiableConstraintDemo()\n      stdout <- Stdout()\n\n      //Inclusive bounds admit both ends\n      low <- Digit(0)\n      high <- Digit(9)\n      stdout.println(`Digit 0 set: ${low?}`)\n      stdout.println(`Digit 9 set: ${high?}`)\n\n      //A single-value range still admits its one value\n      five <- ExactlyFive(5)\n      stdout.println(`ExactlyFive set: ${five?}`)\n\n      //The continuous case: nothing lies between 0 and 1 for an Integer, but plenty does for a Float\n      half <- Fraction(0.5)\n      stdout.println(`Fraction 0.5 set: ${half?}`)\n\n      //'or' admits both sides of the gap\n      lowSide <- Outlier(5)\n      highSide <- Outlier(95)\n      stdout.println(`Outlier 5 set: ${lowSide?}`)\n      stdout.println(`Outlier 95 set: ${highSide?}`)\n\n      //The pinned value satisfies its own constraint\n      answer <- Answer(42)\n      stdout.println(`Answer set: ${answer?}`)\n\n      //Out of range values use the fallible factory rather than a bare constructor\n      tooBig <- Digit().of(10)\n      stdout.println(`Digit 10 set: ${tooBig?}`)","migrationContext":"Java: Bean Validation @Min/@Max are runtime annotations - a contradictory pair is never detected. Python: no type-level constraints at all. Rust: a newtype's validation is hand-written, so an impossible condition compiles and always returns Err. Kotlin: a value class init check with contradictory requires compiles and always throws. EK9: the constraint is part of the TYPE, so an unsatisfiable one is a compile error at the declaration.","keywords":["E08261","bounds","constrain as","constrained","discrete","empty range","impossible","unsatisfiable"],"primaryTopics":["constrained type","unsatisfiable constraint"],"typicalErrors":[{"error":"E08261","correct":"Digit as Integer constrain as\n      >= 0 and <= 9","incorrect":"Digit as Integer constrain as\n      > 0 and < 1","explanation":"Over a whole-number base the strict operators exclude the bounds themselves, so '> 0 and < 1' leaves nothing at all between them. Use the inclusive '>=' and '<=' when the bounds are the values you mean to allow. Note the same shape over a Float base is fine - '> 0.0 and < 1.0' admits 0.5 - because a continuous type has values between the bounds. See ek9 -h E08261 for details."},{"error":"E08261","correct":"ExactlyFive as Integer constrain as\n      >= 5 and <= 5","incorrect":"ExactlyFive as Integer constrain as\n      >= 5 and <= 4","explanation":"The bounds are the right shape but the wrong way round, so the lower bound sits above the upper one and the range is empty. Read the two clauses as a range and check the smaller value is the one with '>='. See ek9 -h E08261 for details."},{"error":"E08261","correct":"Outlier as Integer constrain as\n      <= 10 or >= 90","incorrect":"Outlier as Integer constrain as\n      >= 90 and <= 10","explanation":"'and' means both clauses must hold at once, which nothing outside a single range can do. To allow values on either side of a gap use 'or' - that is a union of two ranges rather than an impossible intersection. See ek9 -h E08261 for details."},{"error":"E08261","correct":"Answer as Integer constrain as\n      == 42","incorrect":"Answer as Integer constrain as\n      == 42 and > 100","explanation":"An '==' pins the only candidate the type can ever hold, so every other clause can only take that value away. Either drop the '==' and use a range, or drop the clause that contradicts it. See ek9 -h E08261 for details."},{"error":"E08262","correct":"Budget as Money constrain as\n      > 330#GBP and < 450#GBP","incorrect":"Budget as Money constrain as\n      > 330#GBP and < 450#USD","explanation":"A suffixed value has two parts and only one of them is a magnitude - '330#GBP' is an amount AND a currency. EK9 does not order values across the suffix, so a cross-currency comparison is UNSET, and an unset result never satisfies a constraint. The same applies to units of measure: '>= 500mm and <= 90cm' mixes two units and they do not convert automatically. Express every bound in one currency or one unit. See ek9 -h E08262 for details."}],"companions":[]}
{"id":1377,"category":"Dispatcher Validation","question":"Can a dispatcher method dispatch on function types?","url":"https://ek9.io/qa/QA1377.html","alternatePhrasings":["Can I use a dispatcher with functions in EK9?","How do I dispatch on an abstract function?","Do EK9 dispatchers work with function types or only classes?","Can a function be a dispatcher in EK9?","How do I route to different handlers based on which function was passed?","Dispatcher over a function hierarchy in EK9","Are EK9 functions polymorphic enough for dispatch?"],"answer":"Yes. EK9 functions are polymorphic, so a dispatcher handles a function hierarchy exactly as it handles a class or trait hierarchy.\n\nA FUNCTION IS NOT A DISPATCHER; A CLASS METHOD IS\nThe 'as dispatcher' marker goes on a method of a class. A function cannot itself be a dispatcher. What a function can be is the thing dispatched ON - the dispatcher method takes the abstract base function as its parameter, and the handlers take its sub-functions.\n\nTHE SHAPE\n  defines function\n    Processor() as pure abstract\n      -> inputText as String\n      <- outputText as String?\n\n    UpperProcessor() extends Processor as pure\n      -> inputText as String\n      <- outputText as String: inputText.upperCase()\n\n  defines class\n    Describer\n      describe() as dispatcher\n        -> p as Processor            //the abstract base function\n        <- rtn as String: \"some Processor\"\n\n      describe()\n        -> p as UpperProcessor       //a sub-function handler\n        <- rtn as String: \"an UpperProcessor\"\n\nDECLARE THE VARIABLE AT THE BASE TYPE\nRouting happens at runtime on the actual type. If you declare the variable at the concrete type, normal overload resolution picks the handler directly at the call site and the dispatcher is never consulted. Declare it at the base function type to exercise dispatch:\n  u as Processor: UpperProcessor\n  result <- describer.describe(u)\n\nSUB-FUNCTIONS WITHOUT A HANDLER REACH THE FALLBACK\nA sub-function with no matching handler runs the dispatcher method's own body, the same as an unhandled subclass does.\n\nTHE TWO RULES THE COMPILER ENFORCES\nGenus must match (E50090): a FUNCTION base takes FUNCTION handlers. You cannot mix a function base with a class handler.\nHandlers must be in the hierarchy (E05210): a handler taking an unrelated function could never match, so it is rejected rather than left as silently dead code.\n\nFUNCTION TYPES ARE NOMINAL, NOT STRUCTURAL\nA function only conforms to another function type if it 'extends' it. A function whose signature merely happens to match 'Supplier of String' is not a 'Supplier of String', so a handler for that type would never fire. Declare an abstract base function and extend it.\n\nSee Q614 for hierarchy rules on classes. See Q619 for two-parameter dispatchers.","ek9Example":"#!ek9\ndefines module qa.dispatchervalidation.functions\n\n  defines function\n\n    //The abstract base function - the function analogue of an abstract class or trait.\n    Processor() as pure abstract\n      -> inputText as String\n      <- outputText as String?\n\n    UpperProcessor() extends Processor as pure\n      -> inputText as String\n      <- outputText as String: inputText.upperCase()\n\n    LowerProcessor() extends Processor as pure\n      -> inputText as String\n      <- outputText as String: inputText.lowerCase()\n\n    //No handler for this one - it must reach the dispatcher's own body.\n    ReverseProcessor() extends Processor as pure\n      -> inputText as String\n      <- outputText as String: inputText\n\n    //Same SIGNATURE as Processor but does NOT extend it. EK9 function conformance is\n    //NOMINAL, so a dispatcher over Processor can never route to a handler taking this -\n    //which is what the E05210 pair below demonstrates.\n    Unrelated() as pure\n      -> inputText as String\n      <- outputText as String: inputText\n\n  defines class\n\n    //CORRECT: the dispatcher entry takes the abstract base FUNCTION, handlers take sub-functions.\n    Describer\n\n      describe() as dispatcher\n        -> p as Processor\n        <- rtn as String: \"some Processor\"\n\n      describe()\n        -> p as UpperProcessor\n        <- rtn as String: \"an UpperProcessor\"\n\n      describe()\n        -> p as LowerProcessor\n        <- rtn as String: \"a LowerProcessor\"\n\n  defines function\n\n    testFunctionDispatch()\n      describer <- Describer()\n\n      //Declared at the BASE function type, so the call site resolves to the dispatcher entry\n      //and the routing decision is made at runtime on the actual function type.\n      u as Processor: UpperProcessor\n      l as Processor: LowerProcessor\n      r as Processor: ReverseProcessor\n\n      require describer.describe(u) == \"an UpperProcessor\"\n      require describer.describe(l) == \"a LowerProcessor\"\n\n      //ReverseProcessor has no handler, so it runs the dispatcher method body.\n      require describer.describe(r) == \"some Processor\"","migrationContext":"Java: no equivalent - functional interfaces are structural at the lambda site but you cannot dispatch on which lambda was passed; you would carry a tag or use the visitor pattern. Kotlin: function types are structural and erased, so 'when (f) { is (String)->String -> ... }' cannot discriminate. Scala: closest with sealed traits wrapping functions, but the wrapper is explicit. Rust: dyn Fn trait objects carry no discriminant; you would use an enum. EK9: functions form a real named hierarchy, so each concrete function is its own type and the dispatcher routes on it directly.","keywords":["E05210","E50090","abstract","dispatch","dispatcher","extends","fallback","function","genus","handler","hierarchy","nominal","polymorphic","routing"],"primaryTopics":["dispatcher function types","function dispatcher","polymorphic function dispatch"],"typicalErrors":[{"error":"E50090","correct":"describe()\n        -> p as UpperProcessor","incorrect":"describe()\n        -> p as Describer","explanation":"The dispatcher entry takes the function Processor, so every handler parameter must also be a function. Mixing a FUNCTION base with a CLASS handler is an incompatible genus. See ek9 -h E50090 for details."},{"error":"E05210","correct":"describe()\n        -> p as LowerProcessor","incorrect":"describe()\n        -> p as Unrelated","explanation":"A handler taking Unrelated can never match, because Unrelated does not extend Processor. Function conformance in EK9 is nominal - matching the signature is not enough. Declare the handler's type with 'extends Processor'. See ek9 -h E05210 for details."}],"companions":[]}
{"id":1378,"category":"Dispatcher Validation","question":"How do I dispatch over a mix of classes and functions in one dispatcher?","url":"https://ek9.io/qa/QA1378.html","alternatePhrasings":["Can one dispatcher handle both objects and functions in EK9?","Why would I use Any as a dispatcher base type?","What is Any good for on a dispatcher?","How do I dispatch over a heterogeneous List of Any?","Can a dispatcher route a record, a class and a function?","Dispatching across unrelated types in EK9","What is the common supertype of a class and a function in EK9?"],"answer":"Declare the dispatcher entry point with 'Any' as its parameter type. 'Any' is the ONLY common super of an aggregate and a function, so it is the only way to express dispatch over a mix of the two.\n\nWHY 'Any' RATHER THAN A BASE YOU DECLARE\nNormally a dispatcher takes an abstract class, a trait or an abstract function as its base, and handlers take types under it. That is preferred: the base names the contract, and the compiler can check handlers against it.\n\nBut some sets of types have no declarable common base. Built-in types such as Integer, Float and Date are closed - you cannot make them extend a type of yours. A class and a function are in different genus families entirely. For those, 'Any' is not laziness; it is the only supertype that exists.\n\nTHE SHAPE\n  describe() as dispatcher\n    -> item as Any                //the entry point, and the fallback body\n    <- rtn as String: \"something else\"\n\n  describe()\n    -> item as Integer            //a built-in\n    <- rtn as String: `Integer ${item}`\n  describe()\n    -> item as Renderable         //a trait - routes the classes implementing it\n    <- rtn as String: `Renderable ${item.render()}`\n  describe()\n    -> item as Point              //a record\n    <- rtn as String: `Point ${item.x},${item.y}`\n  describe()\n    -> item as Formatter          //an abstract FUNCTION - routes its sub-functions\n    <- rtn as String: `Formatter says ${item(\"hi\")}`\n\nA heterogeneous list holds them together when its element type is DECLARED as 'List of Any' (an undeclared mix that shares no type but 'Any' is E50030):\n  items as List of Any: [ 1, Widget(), point, ShoutFormatter ]\n  for item in items\n    stdout.println(d.describe(item))\n\nWHAT YOU KEEP, AND WHAT YOU GIVE UP\nAn 'Any' base is still checked. Handlers are still checked for ambiguity, still checked for reachability (E05280), and the entry point still requires a body (E07120) which catches every type no handler matches.\n\nWhat you give up is exhaustiveness. With a sealed trait the compiler can prove every permitted type has a handler (E05260); with 'Any' the set is open, so the fallback body is the only guarantee. Write it as a real answer, not as a placeholder.\n\nEVERY HANDLER STILL NEEDS SOMETHING THAT CAN REACH IT\n'Any' admits every type, but a handler is still dead if nothing can have its type. A handler for a trait nothing implements, or for an abstract function nothing extends, is reported as E05280. This bites most often with parameterised generic function types: a named function cannot extend 'Consumer of Integer', so the conforming value has to be a dynamic function.\n\nSee Q1377 for dispatching over a function hierarchy alone. See Q614 for the hierarchy rules on classes. See Q254 for the Any type itself.","ek9Example":"#!ek9\ndefines module qa.dispatchervalidation.anymixed\n\n  defines trait\n\n    Renderable\n      render() as pure\n        <- rtn as String?\n\n  defines record\n\n    Point\n      x as Integer: 0\n      y as Integer: 0\n      default operator ?\n\n  defines function\n\n    //An abstract FUNCTION. Nothing but 'Any' is a common super of this and a class.\n    Formatter() as pure abstract\n      -> inputText as String\n      <- outputText as String?\n\n    ShoutFormatter() extends Formatter as pure\n      -> inputText as String\n      <- outputText as String: inputText.upperCase()\n\n  defines class\n\n    Widget with trait of Renderable\n      override render() as pure\n        <- rtn as String: \"Widget\"\n      default operator ?\n\n    //CORRECT: 'Any' is the only base that can take a built-in, a trait, a record AND a function.\n    Describer\n\n      describe() as dispatcher\n        -> item as Any\n        <- rtn as String: \"something else\"\n\n      describe()\n        -> item as Integer\n        <- rtn as String: `Integer ${item}`\n\n      describe()\n        -> item as Renderable\n        <- rtn as String: `Renderable ${item.render()}`\n\n      describe()\n        -> item as Point\n        <- rtn as String: `Point ${item.x},${item.y}`\n\n      describe()\n        -> item as Formatter\n        <- rtn as String: `Formatter says ${item(\"hi\")}`\n\n  defines function\n\n    testAnyMixedDispatch()\n      describer <- Describer()\n\n      point <- Point()\n      point.x: 2\n      point.y: 3\n\n      //Declared 'List of Any' - the only type that can hold all four (undeclared, the mix is E50030).\n      items as List of Any: [ 1, Widget(), point, ShoutFormatter ]\n\n      //Each element is typed 'Any', so every call resolves to the dispatcher ENTRY POINT and the\n      //routing decision is made at runtime on the actual type. Declare at a concrete type instead\n      //and normal overload resolution picks the handler directly, never consulting the dispatcher.\n      results <- List() of String\n      for item in items\n        results += describer.describe(item)\n\n      require results contains \"Integer 1\"\n      require results contains \"Renderable Widget\"\n      require results contains \"Point 2,3\"\n      require results contains \"Formatter says HI\"\n\n      //Nothing handles a Date, so it reaches the dispatcher's own body.\n      other as Any: 2026-09-10\n      require describer.describe(other) == \"something else\"","migrationContext":"Java: no single mechanism - you would use instanceof chains or pattern matching over Object, and a lambda cannot be told apart from another lambda at runtime. Kotlin: 'when (x) { is ... }' over Any, but function types are erased so the function arm cannot discriminate. Python: functools.singledispatch works on any first argument, closest in spirit, but has no static checking. EK9: the dispatch is declared once in the receiver, the compiler checks every handler is reachable and unambiguous, and functions are real named types so they route like any other.","keywords":["Any","E05280","E07120","List of Any","common supertype","dispatch","dispatcher","fallback","function","genus","heterogeneous","mixed","record","trait"],"primaryTopics":["mixed dispatcher","heterogeneous dispatcher","Any dispatcher"],"typicalErrors":[{"error":"E05280","correct":"    ShoutFormatter() extends Formatter as pure","incorrect":"    ShoutFormatter() as pure","explanation":"With nothing extending the abstract function Formatter, no value can ever have that type, so the handler can never be selected. An 'Any' base does not change this - it admits every type that EXISTS. See ek9 -h E05280 for details."},{"error":"E07120","correct":"describe() as dispatcher\n        -> item as Any\n        <- rtn as String: \"something else\"","incorrect":"describe() as dispatcher\n        -> item as Any\n        <- rtn as String?","explanation":"The dispatcher entry point must provide a body. With an 'Any' base the type set is open, so that body is the only thing standing between an unhandled type and no answer at all. See ek9 -h E07120 for details."}],"companions":[]}
{"id":1379,"category":"Safe Value Access","question":"Why can't I call get() in a do-while body when the trailing while condition checks the Optional?","url":"https://ek9.io/qa/QA1379.html","alternatePhrasings":["Does a do-while trailing condition make an Optional safe in EK9?","Why do I get E08030 inside a do-while loop in EK9?","How do I safely unwrap an Optional in a do-while loop?","do ... while maybeValue? still says has not been checked before access","Safe access in a post-condition loop in EK9","Why does while o? work but do ... while o? not?","How do I guard a do-while loop in EK9?"],"answer":"No. A do/while checks its condition AFTER the body has already run, so the trailing 'while' cannot make anything in the body safe. Writing 'do ... while maybeName?' gives E08030 on the get() inside the body, and that is correct - by the time the check runs, the unsafe call has already happened.\n\nWHY THE TRAILING CONDITION CANNOT HELP\nSafe access works by proving the check happens BEFORE the access. A leading 'while maybeName?' checks first, so the body is safe. A do/while inverts that order:\n  maybeName <- Optional(\"Steve\")\n  do\n    stdout.println(maybeName.get())   //E08030 - runs BEFORE the check\n  while maybeName?\nThe condition still controls whether you iterate again; it just cannot retroactively guarantee the first pass.\n\nTHREE WAYS THAT DO WORK\n1. Guard from outside with 'if':\n  if maybeName?\n    do\n      stdout.println(maybeName.get())\n      loopDone: true\n    while not loopDone\n\n2. Use a PRE-FLOW guard on the 'do' itself. It sits between 'do' and the body, so it is evaluated first:\n  do maybeName <- lookupName()\n    stdout.println(maybeName.get())\n    loopDone: true\n  while not loopDone\n\n3. Use a leading 'while' instead, if the loop does not need to run at least once:\n  while maybeName?\n    stdout.println(maybeName.get())\n\nA PRE-FLOW GUARD OVERRIDES THE AT-LEAST-ONCE RULE\nThis surprises people. A do/while normally runs its body at least once, but a pre-flow guard is a real guard - if the value is unset the body runs ZERO times:\n  do maybeName <- lookupMissingName()\n    stdout.println(\"never printed\")\n  while not loopDone\nThe guard is a precondition on entering the loop at all, not part of the continuation condition.\n\nWHICH CONDITIONS THE COMPILER CAN ANALYSE\nAn is-set check proves its variable when it is a top-level 'and' operand of the condition, whatever the other operands are:\n  - while maybeName? and stillGoing makes the body safe\n  - while maybeName? and not loopDone makes the body safe too - 'not' applies to loopDone, not to the check\n  - while maybeName? or useDefault does NOT, giving E08030\n'or' genuinely breaks the guarantee, and a check under 'not' ('not (not maybeName? or giveUp)') or inside a call argument proves nothing either. If you hit this, hoist the check into an enclosing 'if' or a pre-flow guard.\n\nDO NOT REASSIGN THE GUARDED VARIABLE\nInside a scope made safe by a check, reassigning that variable is E08040. The guarantee was established once; letting you overwrite it would invalidate it.\n\nSee Q67 for do-while basics. See Q163 for unwrapping Optional. See Q165 for why there is no unchecked get. See Q166 for the consistent safe-access pattern.","ek9Example":"#!ek9\ndefines module qa.safeaccess.dowhiletrailing\n\n  defines function\n\n    <?-\n      Returns a set Optional.\n    -?>\n    lookupName() as pure\n      <- rtn <- Optional(\"Steve\")\n\n    <?-\n      Returns an unset Optional, to show a pre-flow guard refusing to enter the loop.\n    -?>\n    lookupMissingName() as pure\n      <- rtn as Optional of String: Optional() of String\n\n  defines program\n\n    DoWhileSafeAccessDemo()\n      stdout <- Stdout()\n\n      // === GUARD FROM OUTSIDE WITH 'if' ===\n      // The check runs before the loop, so the body is safe.\n\n      outerName <- lookupName()\n      outerDone <- false\n      if outerName?\n        do\n          stdout.println(`Outer if guard: ${outerName.get()}`)\n          outerDone: true\n        while not outerDone\n\n      // === PRE-FLOW GUARD ON THE 'do' ITSELF ===\n      // Sits between 'do' and the body, so it is evaluated first.\n\n      preFlowDone <- false\n      do preFlowName <- lookupName()\n        stdout.println(`Pre-flow guard: ${preFlowName.get()}`)\n        preFlowDone: true\n      while not preFlowDone\n\n      // === A PRE-FLOW GUARD OVERRIDES THE AT-LEAST-ONCE RULE ===\n      // The Optional is unset, so the body runs ZERO times.\n\n      skippedDone <- false\n      do missingName <- lookupMissingName()\n        stdout.println(`Never printed: ${missingName.get()}`)\n        skippedDone: true\n      while not skippedDone\n      stdout.println(\"Unset pre-flow guard skipped the body entirely\")\n\n      // === A LEADING 'while' CAN MAKE THE BODY SAFE ===\n      // Checked before each iteration, so the ordering requirement is met.\n      // Each top-level 'and' operand counts; a check under 'or' or 'not' proves nothing.\n\n      leadingName <- lookupName()\n      stillGoing <- true\n      while leadingName? and stillGoing\n        stdout.println(`Leading while: ${leadingName.get()}`)\n        stillGoing: false","migrationContext":"Java: Optional has no loop integration; you call isPresent() then get(), and a do-while gives no help either way - the compiler never checks. Kotlin: smart casts do not survive into a do-while body from the trailing condition, and null-safety is enforced by the type system rather than by flow position, so you use '?.let' or '!!'. Swift: 'repeat { } while' cannot bind an optional; 'guard let' before the loop is the idiom, which is closest to EK9's outer-if form. Rust: 'loop { }' with 'if let Some(v) = opt' inside re-matches every iteration; there is no trailing-condition binding. Go: no Optional; a nil check before the loop, unverified by the compiler. C#: nullable reference analysis does flow-sensitive checks but treats a do-while trailing condition as not guarding the body, the same conclusion EK9 reaches - though C# emits a warning where EK9 emits an error. EK9: the check must be positioned before the access, and the compiler proves it; a pre-flow guard on 'do' is the construct that puts a check before a post-condition loop body.","keywords":["E08030","E08040","access","condition","do","get","guard","iterator","loop","optional","postcondition","preflow","result","safe","trailing","unwrap","while"],"primaryTopics":["do while safe access","trailing condition guard"],"typicalErrors":[{"error":"E08030","correct":"      do preFlowName <- lookupName()\n        stdout.println(`Pre-flow guard: ${preFlowName.get()}`)","incorrect":"      preFlowName <- lookupName()\n      do\n        stdout.println(`Pre-flow guard: ${preFlowName.get()}`)","explanation":"The trailing 'while' is evaluated after the body has already run, so it cannot make get() safe. Move the check in front of the body as a pre-flow guard on the 'do'. See ek9 -h E08030 for details."},{"error":"E08030","correct":"while leadingName? and stillGoing","incorrect":"while leadingName? or stillGoing","explanation":"Safe access is only inferred from an is-set check that is a top-level 'and' operand of the condition. Putting it under 'or' (or under 'not') means the body can run while the variable is unset, so the body is not marked safe. Keep the is-set check as a plain 'and' operand. See ek9 -h E08030 for details."},{"error":"E08040","correct":"        stdout.println(`Leading while: ${leadingName.get()}`)\n        stillGoing: false","incorrect":"        stdout.println(`Leading while: ${leadingName.get()}`)\n        leadingName: lookupName()\n        stillGoing: false","explanation":"Inside a scope that a check has made safe, the guarded variable cannot be reassigned - that would invalidate the guarantee the check established. End the loop with a separate flag instead. See ek9 -h E08040 for details."}],"companions":[]}
{"id":1380,"category":"Getting Started","question":"How do I split my code across multiple .ek9 files in one project?","url":"https://ek9.io/qa/QA1380.html","alternatePhrasings":["Why does my other file's function give E50001 not resolved?","How do I compile more than one .ek9 file together?","What is applyStandardIncludes and when do I need it?","How do I set up a multi-file EK9 project?","Do I need a package.ek9 and what goes in it?","My second file's types are not resolving - what am I missing?","What is the difference between includeFiles and applyStandardIncludes?","How does the EK9 compiler know which source files to build?","How does the compiler find all my source files?","Why is my other file not resolved?","Why can my program not see code from the next file along?"],"answer":"Compiling one file compiles exactly that ONE file. `ek9 -c Main.ek9` does not look at its neighbours, so the moment a second file exists you get E50001 'not resolved' for anything declared in it - even when both files declare the SAME module.\n\nTHE SYMPTOM\n  Greeting.ek9 -> defines function greet()\n  Main.ek9     -> stdout.println(greet(\"Steve\"))\n  ek9 -c Main.ek9\n  Error: E50001: 'greet' on line 8 position 21: not resolved\n\nThis is NOT a module-scoping problem and a 'references' block will not fix it. References brings in symbols from a DIFFERENT module; it does not add a file to the build. What is missing is a package that says which files the build contains.\n\nADD A package.ek9\nCreate a file declaring the same module with a 'defines package' block. Then compile THAT file:\n  #!ek9\n  defines module com.example.app\n    defines package\n      version <- 1.0.0-1\n      description <- \"My app\"\n      license <- \"MIT\"\n      applyStandardIncludes <- true\n\n  ek9 -C package.ek9\n\nFIVE LEVELS, IN ORDER OF NEED\n1. Single file - no package at all. Just write it and run it.\n2. Several files, named explicitly - includeFiles <- [ \"Greeting.ek9\", \"Main.ek9\" ]\n3. Every .ek9 in the directory - applyStandardIncludes <- true\n4. Dependencies - add deps <- { \"module\": \"version\" }\n5. Publishing - add publicAccess, tags, license\n\nMost projects want level 3. The practical difference from level 2: with applyStandardIncludes you drop a new .ek9 file into the directory and it is picked up with NO edit to package.ek9, whereas includeFiles must name every file and silently omits any you forget.\n\nWHICH ONE SHOULD I USE\nUse includeFiles when you deliberately want only some files compiled. Use applyStandardIncludes for an ordinary project. They can be combined with applyStandardExcludes and excludeFiles to subtract.\n\nRUNNING IT\nPrograms are run from the built artefact under .ek9/, naming the program fully qualified:\n  java -jar .ek9/package.jar -r com.example.app::MyProgram\nNote that 'ek9 -r MyProgram package.ek9' does not work: the -r flag looks for the program in the file you name, and package.ek9 declares none.\n\nONE MODULE OR MANY\nFiles in one project may all declare the same module, or different modules. Same module means they share a scope and see each other directly. Different modules need a 'references' block to pull names across. Either way the package block is what decides which files are compiled.\n\nSee Q6 for module organisation. See Q13 for package versus module. See Q7 for dependencies. See Q805 for where test code lives.","ek9Example":"#!ek9\ndefines module qa.getting.started.multiple.files\n\n  <?-\n    This file carries a real package block. applyStandardIncludes is what makes a\n    directory of .ek9 files into one build; without it, compiling any single file\n    compiles only that file and its siblings' declarations are E50001 'not resolved'.\n  -?>\n  defines package\n\n    version <- 1.0.0-1\n    description <- \"Shows the package block that turns several files into one build\"\n    license <- \"MIT\"\n\n    //Level 3 - every .ek9 in this directory. Drop in a new file and it is picked up\n    //with no edit here. Level 2 would instead be:\n    //  includeFiles <- [ \"Greeting.ek9\", \"Main.ek9\" ]\n    //which must name every file and silently omits any you forget.\n    applyStandardIncludes <- true\n\n  defines function\n\n    //In a real project this would sit in its own file, say Greeting.ek9.\n    greet() as pure\n      -> name as String\n      <- rtn as String: `Hello, ${name}!`\n\n  defines program\n\n    //And this in Main.ek9. Both declare the same module, so they see each other\n    //directly - but ONLY because the package block puts both in the build.\n    MultipleFilesDemo()\n      stdout <- Stdout()\n\n      stdout.println(greet(\"Steve\"))\n      stdout.println(\"Compile the package file: ek9 -C package.ek9\")\n      stdout.println(\"A 'references' block is for other MODULES; it does not add a file to the build\")","migrationContext":"Java: the build tool (Maven/Gradle) owns the source set - src/main/java is compiled wholesale and javac is never invoked per file. Python: no build file; any .py next to another is importable, so this problem does not arise. Rust: Cargo.toml plus 'mod' declarations - a file not reachable from a mod tree is silently not compiled, the closest analogue to forgetting a file in includeFiles. Go: every .go file in a directory is one package, compiled together automatically, closest to applyStandardIncludes. C/C++: the build system lists translation units explicitly, like includeFiles. EK9: packaging is part of the language rather than an external tool - 'defines package' in a .ek9 file IS the build file, and there is no Maven, Gradle or npm to install.","keywords":["E50001","applyStandardIncludes","build","compile","directory","file","files","includeFiles","layout","module","multifile","multiple","package","project","references","resolved","sibling","source","sources","split","structure"],"primaryTopics":["multiple source files","package includes"],"typicalErrors":[{"error":"E50001","correct":"stdout.println(greet(\"Steve\"))","incorrect":"stdout.println(greetUser(\"Steve\"))","explanation":"E50001 is what the CALLER sees when a declaration is not in the build - the symptom a sibling file left out of the package produces. Calling a name that is not declared reproduces it inside this one file: the call says greetUser(...) but only greet() exists, so it does not resolve. NOTE the package-level CAUSE is deliberately not a typicalError: a mutation of the package block cannot unresolve anything here, because this example inlines the function and the program in ONE file - so the two pairs that tried it (dropping applyStandardIncludes, and an includeFiles list missing a file) mutated text that changes nothing and silently tested nothing. That cause is taught in the prose and comments above instead. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1381,"category":"Control Flow","question":"Can I put a guard on a stream 'for' pipeline?","url":"https://ek9.io/qa/QA1381.html","alternatePhrasings":["Why does my guarded stream pipeline give E08056?","Can I use a guard with 'collect as' in EK9?","How do I guard a stream for-range?","Guard on a stream pipeline expression not allowed","How do I skip a whole stream pipeline when a value is unset?","Why is a guard fine on a stream statement but not a stream expression?","E08056 guarded stream as expression"],"answer":"Yes for the statement form, no for the expression form.\n\nA stream's 'for' source is a for-range, so it takes exactly the same pre-flow as the statement loop. As a STATEMENT the guard gates the whole pipeline - if the guarded value is unset, nothing is piped at all:\n\n  for limit <- getLimit() then i in 1 ... 3 > stdout\n  //unset limit: nothing printed. Set limit: 1 2 3.\n\nAS AN EXPRESSION IT IS AN ERROR (E08056)\n  collected <- for limit <- getLimit() then i in 1 ... 3 | collect as List of Integer\n  //E08056\n\nEvery other guarded expression form declares its own result, and that declaration's initial value IS the result when the guard skips - 'the initialiser is the guard's else':\n\n  result <- switch target :=? getValue() with target\n    <- rtn as String: \"init\"        //this is the answer if the guard skips\n    case 1\n      rtn: \"one\"\n    default\n      rtn: \"other\"\n\nA stream expression ends in '| collect as T'. It has no returning param, so there is nowhere to write that initial value, and the result would be whatever T's default constructor produces. That differs by type in a way nothing at the call site shows: 'collect as List of Integer' would give an empty but SET list, while 'collect as Integer' would give an UNSET Integer. Rather than let that depend on the collect type, the compiler rejects the shape.\n\nTWO WAYS TO WRITE WHAT YOU MEANT\n1. Statement form with your own accumulator, so you choose the value that survives a skipped guard:\n     total <- 0\n     for limit <- getLimit() then i in 1 ... 3\n       total += i\n\n2. Hoist the guard into an enclosing 'if', leaving the stream unguarded:\n     if limit <- getLimit()\n       collected <- for i in 1 ... 3 | collect as List of Integer\n\n':=' IS NOT A GUARD\nOnly the set-checking pre-flows gate the body: '<-' and ':=?'. A plain ':=' always assigns and the pipeline always runs, so it is accepted in both forms - and it is not doing what a reader might assume:\n  for target := getLimit() with i in 1 ... 2 > stdout   //always runs, even if getLimit() is unset\n\nA GUARD VARIABLE BELONGS TO ITS STREAM\nA '<-' guard declares a variable for that stream only: the range and the stages may use it ('for limit <- getLimit() then i in 1 ... 9 | head limit > stdout'), and it is gone after the pipeline - naming it afterwards is E50001. The loop variable is the stream's too, so two streams in one block may each declare 'limit' and each call their loop variable 'i'. If you need the value after the stream, declare it first (uninitialised) and guard it with ':=?'.\n\nSee Q837 for guards in expression forms generally. See Q1357 for the for-in expression form. See Q89 for stream pipelines. See Q74 for guards in if statements.","ek9Example":"#!ek9\ndefines module qa.controlflow.streamguard\n\n  defines function\n\n    getLimit() as pure\n      <- rtn as Integer: Integer()\n\n  defines program\n\n    StreamGuardDemo()\n      stdout <- Stdout()\n\n      // === STATEMENT FORM WITH A GUARD - LEGAL ===\n      // The guard gates the whole pipeline. getLimit() is unset here, so nothing is piped.\n\n      for skippedLimit <- getLimit() then skippedItem in 1 ... 3 > stdout\n      stdout.println(\"unset guard skipped the pipeline\")\n\n      // === THE EXPRESSION FORM WOULD BE E08056 ===\n      // collected <- for limit <- getLimit() then i in 1 ... 3 | collect as List of Integer\n      // There is no returning param on which to say what the result is when the guard skips.\n\n      // === WRITE IT AS A STATEMENT WITH YOUR OWN ACCUMULATOR ===\n      // Now YOU choose the value that survives a skipped guard.\n\n      total <- 0\n      for accumulatedLimit <- getLimit() then accumulatedItem in 1 ... 3\n        total += accumulatedItem\n      stdout.println(`total after skipped guard: ${total}`)\n\n      // === OR HOIST THE GUARD INTO AN 'if', LEAVING THE STREAM UNGUARDED ===\n\n      if hoistedLimit <- getLimit()\n        collected <- for hoistedItem in 1 ... 3 | collect as List of Integer\n        stdout.println(`collected ${length collected}`)\n      stdout.println(\"hoisted guard skipped the block\")\n\n      // === ':=' IS NOT A GUARD - THE PIPELINE ALWAYS RUNS ===\n\n      plainTarget as Integer?\n      for plainTarget := getLimit() with plainItem in 1 ... 2 > stdout\n      stdout.println(`plain assign ran anyway, target set? ${plainTarget?}`)","migrationContext":"Java: Stream.of(...) has no guard concept; you write an if around the stream, or an empty-stream branch. Kotlin: '?.let { }' around the sequence, or 'takeIf'. Rust: iterators are lazy and a None short-circuits via '?', but there is no pre-flow that skips the whole chain. Python: a conditional expression around a comprehension. EK9: the guard is part of the loop head in both loop and stream form, and the compiler refuses the expression shape rather than silently handing back whatever the collect type default-constructs.","keywords":["E08056","E50001","accumulator","collect","collect as","expression","for","forrange","guard","pipeline","preflow","skip","statement","stream","unset"],"primaryTopics":["guard on stream pipeline","stream expression guard"],"typicalErrors":[{"error":"E08056","correct":"      total <- 0\n      for accumulatedLimit <- getLimit() then accumulatedItem in 1 ... 3\n        total += accumulatedItem","incorrect":"      total <- for accumulatedLimit <- getLimit() then accumulatedItem in 1 ... 3 | collect as List of Integer","explanation":"A guard can skip the whole pipeline, and a stream expression has no returning param on which to declare the value produced on the skip path. Use the statement form with your own accumulator, or hoist the guard into an enclosing 'if'. See ek9 -h E08056 for details."},{"error":"E50001","correct":"      for skippedLimit <- getLimit() then skippedItem in 1 ... 3 > stdout","incorrect":"      for skippedLimit <- getLimit() then skippedItem in 1 ... 3 > stdout\n      stdout.println(`limit was ${skippedLimit}`)","explanation":"A '<-' guard declares a variable that belongs to its stream: the range and the stages can use it, but it is gone once the pipeline ends, so naming it afterwards is not resolved. Declare the variable before the stream (uninitialised) and guard it with ':=?' if you need it afterwards. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1382,"category":"Advanced Type System","question":"How do I work with raw bytes in EK9?","url":"https://ek9.io/qa/QA1382.html","alternatePhrasings":["Is there a byte type in EK9?","How do I represent a single octet of binary data?","Why can I not add two Bytes together?","How do I convert a Byte to hex text and back?"],"answer":"Byte is an unsigned 8-bit value - 0 to 255 - and it is the raw-data scalar, not a small number. It is tri-state like every EK9 type.\n\nCREATING\nThere is no Byte literal. Construct from an Integer, from hex text, or by copy:\n  fromInteger <- Byte(255)\n  fromHex <- Byte(\"0xFF\")     // 0x prefix optional\n  bare <- Byte(\"FF\")\n  copied <- Byte(fromInteger)\nOut of range gives UNSET rather than a wrapped value, so a bad octet is visibly absent instead of quietly wrong:\n  Byte(256)?    // false\n  Byte(-1)?     // false\n\nNO ARITHMETIC - THIS IS DELIBERATE\nByte has NO +, -, *, / and no ++ or --. The question 'what is 0xFF + 1?' cannot be asked of a Byte at all, so the wraparound bug class does not exist. Java answers it by making byte SIGNED (0xFF reads back as -1, hence the b & 0xFF idiom everywhere), C leaves it to integer promotion, Go wraps silently to 0. EK9 removes the question.\n\nTo do arithmetic, convert deliberately:\n  total <- aByte.toInteger() + 1\nOr let #^ promote inside an Integer expression. Note the asymmetry - promotion applies to the ARGUMENT:\n- `1 + #^aByte` resolves, and widens: 1 + 0xFF is 256, never 0\n- `aByte + 1` does NOT resolve - Byte has no + of its own\n\nHEX IN, HEX OUT\n$ emits exactly two upper-case hex characters and the String constructor parses hex, so the round trip always holds:\n- `$Byte(15)` gives \"0F\"\n- `Byte($b) == b` is true for all 256 values\nDecimal text is deliberately rejected - Byte(\"255\") is UNSET - because \"10\" cannot mean sixteen to $ and ten to the constructor. Decimal arrives through Byte(Integer).\n\nBITWISE AND SHIFTS\nand, or, xor and ~ work as they do on Bits and Boolean. Shifts stay inside eight bits, so bits shifted out are lost and the value never widens:\n- `Byte(\"0F\") << 4` gives F0\n- `Byte(\"0F\") << 8` gives 00\n>> is always a logical shift because a Byte is unsigned - there is no sign bit to propagate.\n\nCONVERSIONS\n- `aByte.toInteger()` gives 0-255\n- `aByte.toBits()` gives the eight-bit sequence\n- `$$aByte` gives a JSON number 0-255, not the hex text\n\nSee Q40 for Bits (a variable-length bit sequence, a different job). Use 'ek9 -h Byte' for the full API.","ek9Example":"#!ek9\ndefines module qa.byte.usage\n\n  defines program\n\n    ByteUsage()\n      stdout <- Stdout()\n\n      //Construct from hex text or from an Integer - there is no Byte literal.\n      fromHex <- Byte(\"0xFF\")\n      stdout.println(`fromHex: ${fromHex}`)\n\n      //$ always emits exactly two upper-case hex characters.\n      fifteen <- Byte(15)\n      stdout.println(`fifteen: ${fifteen}`)\n\n      //Out of range is UNSET, never a wrapped value.\n      outOfRange <- Byte(256)\n      stdout.println(`outOfRange set: ${outOfRange?}`)\n\n      //Decimal text is not a Byte - the String form is hex in both directions.\n      decimalText <- Byte(\"255\")\n      stdout.println(`decimalText set: ${decimalText?}`)\n\n      //Bitwise operators, as on Bits and Boolean.\n      masked <- Byte(\"AA\") and Byte(\"0F\")\n      stdout.println(`masked: ${masked}`)\n\n      //Shifts stay inside eight bits - what leaves is lost, the value never widens.\n      shifted <- Byte(\"0F\") << 4\n      stdout.println(`shifted: ${shifted}`)\n\n      shiftedOut <- Byte(\"0F\") << 8\n      stdout.println(`shiftedOut: ${shiftedOut}`)\n\n      //No arithmetic on Byte - promote instead, and it widens rather than wrapping.\n      widened <- 1 + #^fromHex\n      stdout.println(`widened: ${widened}`)\n\n      //Conversions.\n      asBits <- Byte(\"AA\").toBits()\n      stdout.println(`asBits: ${asBits}`)\n\n      //The contract that justifies hex-only parsing.\n      roundTripped <- Byte($fifteen)\n      stdout.println(`roundTrip holds: ${roundTripped == fifteen}`)","migrationContext":"Java: byte is SIGNED -128..127, so reading 0xFF from a stream gives -1 and nearly every use is written 'b & 0xFF'; forgetting the mask is a classic binary-parsing bug the compiler cannot see. C: unsigned char is 0-255 but participates in silent integer promotion. Python: no byte scalar at all - indexing a bytes object yields a full-range int. Go: byte is uint8 and arithmetic wraps silently (0xFF + 1 == 0). Rust: u8 arithmetic panics on overflow in debug and wraps in release. EK9: Byte is unsigned 0-255 with NO arithmetic operators, so overflow cannot be expressed; out-of-range construction is UNSET, not wrapped; the String form is hex in BOTH directions so $ round-trips.","keywords":["binary","bitwise","byte","data","hex","hexadecimal","octet","overflow","raw","shift","tristate","unsigned"],"primaryTopics":["Byte","binary data","hex conversion"],"typicalErrors":[],"companions":[]}
{"id":1383,"category":"Advanced Type System","question":"How do I work with binary data (Bytes) in EK9?","url":"https://ek9.io/qa/QA1383.html","alternatePhrasings":["Is there a byte array in EK9?","How do I XOR two buffers together?","How do I convert binary data to hex or Base64?","Why is my xor of two Bytes unset?"],"answer":"Bytes is an immutable sequence of unsigned 8-bit values - the public face of binary data. It follows the sequence semantics of Bits rather than those of a number: + CONCATENATES, | ACCUMULATES, and there is no arithmetic at all.\n\nCREATING\n  payload <- 0yDEADBEEF                 // the LITERAL - 0y then hex digits IN PAIRS\n  fromHex <- Bytes(\"DEADBEEF\")        // hex, 0x prefix optional, case-insensitive\n  single <- Bytes(Byte(\"FF\"))          // one octet\n  packed <- Bytes(someBits)            // whole bytes only\n  zeroed <- Bytes().withLength(16)     // 16 zero bytes\n  filled <- Bytes().withLength(16, Byte(\"FF\"))\nThe withLength pair follows the standard-library 'with' convention - pure, returns a new value, and called on a default-constructed receiver.\n\nTHE LITERAL IS 0y, NOT 0x\n0x stays Integer and 0b stays Bits - all three coexist and none is ambiguous. Hex digits come in PAIRS and the rule is enforced by the LEXER, so 0yF does not parse at all rather than becoming an unset Bytes to be diagnosed later. Case follows 0x/0b: 0Y works too.\n\nEMPTY IS NOT UNSET\nBytes() is UNSET. A zero-length Bytes is SET and empty. Binary protocols depend on that difference - a field that was absent and a field that was present and empty are not the same fact.\n\nHEX BOTH WAYS\n$ emits upper-case hex and the String constructor parses hex, so Bytes($b) == b always holds. An ODD number of hex digits is UNSET - half a byte is not a byte. Decimal text is not accepted.\n\nBase64 is NOT guessed at, because \"DEADBEEF\" is simultaneously valid hex and valid Base64. Name the encoding:\n  fromB64 <- Bytes(text, \"base64\")\n  asB64 <- payload.toBase64()\n\nBITWISE NEEDS EQUAL LENGTHS\nand, or and xor require BOTH operands to be the same length. A mismatch is UNSET. This is deliberate and it is the one place Bytes departs from Bits: zero-extending a short key leaves the tail of the message UNCHANGED, so the 'ciphertext' would carry plaintext while the operation reported success. Length is semantic for binary data - a 16-byte key IS 16 bytes.\n\nSEQUENCE OPERATIONS\n  joined <- first + second            // concatenation\n  extended <- payload + Byte(\"FF\")    // append one octet\n  collector <- Bytes()\n  cat payload.iterator() > collector   // | accumulates through a stream\nOrdering (<, <=, >, >=, <=>) is lexicographic over UNSIGNED octets, so 0xFF sorts ABOVE 0x7F.\n\nSLICING AND SEARCH\nslice(from, count), first(n) and last(n) return VALUES, not views, so a slice can never mutate its source. Out of range is UNSET rather than clamped. indexOf returns UNSET when the needle is absent - there is no -1 to be mistaken for a real index.\n\nCOMPARING SECRETS\nUse constantTimeEquals for MACs, tokens and digests. It examines every byte regardless of where the first difference is; == is free to short-circuit and must not be used for secrets.\n\nSee Q1382 for Byte, the scalar element type. Use 'ek9 -h Bytes' for the full API.","ek9Example":"#!ek9\ndefines module qa.bytes.usage\n\n  defines program\n\n    BytesUsage()\n      stdout <- Stdout()\n\n      //Hex in, hex out - the round trip always holds.\n      //The LITERAL form - 0y then hex digits in pairs. 0x is Integer, 0b is Bits.\n      payload <- 0yDEADBEEF\n      stdout.println(`payload: ${payload}`)\n      stdout.println(`length: ${length payload}`)\n      stdout.println(`base64: ${payload.toBase64()}`)\n\n      //Buffers via the standard 'with' factory convention.\n      zeroed <- Bytes().withLength(4)\n      stdout.println(`zeroed: ${zeroed}`)\n      filled <- Bytes().withLength(4, Byte(\"FF\"))\n      stdout.println(`filled: ${filled}`)\n\n      //'+' concatenates - Bytes has no arithmetic.\n      joined <- Bytes(\"DEAD\") + Bytes(\"BEEF\")\n      stdout.println(`joined: ${joined}`)\n\n      //XOR is its own inverse, which is why low level code leans on it.\n      masked <- payload xor filled\n      stdout.println(`masked: ${masked}`)\n      stdout.println(`recovered: ${(masked xor filled) == payload}`)\n\n      //Unequal lengths are UNSET, never zero extended - zero extension would leave the\n      //tail of the longer operand untouched, which for a cipher means leaking plaintext.\n      mismatch <- payload xor Bytes(\"FF\")\n      stdout.println(`mismatch set: ${mismatch?}`)\n\n      //Slices are values, and absence is unset rather than a -1 sentinel.\n      stdout.println(`first two: ${payload.first(2)}`)\n      stdout.println(`indexOf BEEF: ${payload.indexOf(Bytes(\"BEEF\"))}`)\n      stdout.println(`not found set: ${payload.indexOf(Bytes(\"CAFE\"))?}`)","migrationContext":"Java: byte[] is mutable, has reference equality, no unset state, and every helper is hand-rolled; null vs empty is a constant confusion. C: unsigned char* plus a separate size_t that can drift out of step - the split that makes overruns possible. Python: bytes is immutable but uses None for absent, reintroducing null. Go: []byte slices share backing storage, so a slice can mutate its source. EK9: Bytes is immutable with value equality, carries its own length, is tri-state (unset vs empty vs set), slices are values not views, and and/or/xor REFUSE unequal lengths rather than zero-extending - which in other languages silently leaks the tail of the longer operand.","keywords":["base64","binary","buffer","bytes","constanttime","hex","immutable","mask","octet","sequence","slice","xor"],"primaryTopics":["Bytes","binary data","bitwise operations"],"typicalErrors":[],"companions":[]}
{"id":1384,"category":"Advanced Type System","question":"How do I parse binary data or a network protocol in EK9?","url":"https://ek9.io/qa/QA1384.html","alternatePhrasings":["Is there a ByteBuffer equivalent in EK9?","How do I read a big-endian integer from bytes?","How do I safely parse a length-prefixed binary format?","Why is my readUInt64BE unset?"],"answer":"BytesReader is a forward-only cursor over a Bytes, obtained with aBytes.reader(). It is what you use for binary protocol and format parsing.\n\nTHE CENTRAL IDEA - MALFORMED INPUT IS A VALUE, NOT AN EXCEPTION\nEvery read is tri-state. A read that would run past the end returns UNSET and LEAVES THE CURSOR WHERE IT WAS. So parsing is a sequence of ordinary reads followed by a set-check, with no try/catch and no bounds test before every field. Because a failed read does not move the cursor, a smaller read can still succeed afterwards:\n  cursor <- Bytes(\"010203\").reader()\n  complete <- cursor.readUInt32BE()?   // false - only three bytes\n  start <- cursor.position()           // still 0\n  smaller <- cursor.readUInt16BE()     // 0x0102 - works\n\nCURSOR STATE\n  .position()   bytes consumed so far\n  .remaining()  bytes left\n  .atEnd()      nothing left\n  .peek()       next byte WITHOUT consuming it\n\nENDIANNESS IS ALWAYS EXPLICIT\nThere is no readUInt32() with a default. There is readUInt32BE and readUInt32LE, and nothing else. A silent endianness default is a known bug category - it is correct on the machine it was written on and wrong in production - so the defaulted spelling does not exist. The same applies at every width: readUInt16BE/LE, readUInt64BE/LE, and the signed readInt16BE/LE, readInt32BE/LE, readInt64BE/LE.\n\nSigned reads sign-extend; unsigned ones do not:\n  Bytes(\"fffe\").reader().readInt16BE()   // -2\n  Bytes(\"fffe\").reader().readUInt16BE()  // 65534\n\nWHY readUInt64BE CAN BE UNSET\nEK9's Integer is 64-bit SIGNED, so an unsigned 64-bit value with the top bit set has no correct representation - returning it would hand back a NEGATIVE number for a large positive one. It is UNSET instead, and deliberately NOT consumed, so you can reinterpret the same bytes:\n  wide <- Bytes(\"ffffffffffffffff\").reader()\n  fits <- wide.readUInt64BE()?   // false - will not fit\n  signed <- wide.readInt64BE()   // -1 - same bytes, your interpretation\n\nNAVIGATION\n  .skip(n)    advances; answers false and does NOT move if it would run past the end - it never clamps\n  .readBytes(n)  reads n bytes as a Bytes\n\nSLICE - WHAT MAKES NESTED FORMATS SAFE\n  body <- cursor.slice(declaredLength)\nThis is a bounded sub-reader over the next n bytes which CONSUMES them from the parent. The sub-reader cannot read past its own boundary however malformed its content is, and the parent carries on after it. That is exactly what a length-prefixed structure needs - a TLS extension inside a record, a DER SEQUENCE inside a SEQUENCE.\n\nSee Q1383 for Bytes and Q1382 for Byte. Use 'ek9 -h BytesReader' for the full API.","ek9Example":"#!ek9\ndefines module qa.bytesreader.usage\n\n  defines program\n\n    BytesReaderUsage()\n      stdout <- Stdout()\n\n      //A TLS-record shape: type, version, body length, then a length-prefixed body.\n      record <- Bytes(\"1603030008034142430358595a\")\n      cursor <- record.reader()\n\n      stdout.println(`type: ${cursor.readByte()}`)\n      stdout.println(`version: ${cursor.readUInt16BE()}`)\n\n      bodyLength <- cursor.readUInt16BE()\n      stdout.println(`body length: ${bodyLength}`)\n\n      //A bounded sub-reader - it cannot read past its own boundary however malformed the body is.\n      body <- cursor.slice(bodyLength)\n      fieldLength <- body.readByte().toInteger()\n      stdout.println(`field: ${body.readBytes(fieldLength)}`)\n\n      //slice consumed the body from the parent, so the parent is done.\n      stdout.println(`parent at end: ${cursor.atEnd()}`)\n\n      //A short read is UNSET and leaves the cursor alone, so a smaller read still works.\n      short <- Bytes(\"010203\").reader()\n      stdout.println(`underflow set: ${short.readUInt32BE()?}`)\n      stdout.println(`cursor unmoved: ${short.position()}`)\n      stdout.println(`smaller read: ${short.readUInt16BE()}`)\n\n      //An unsigned 64-bit value with the top bit set cannot fit a SIGNED Integer, so it is\n      //unset rather than negative - and is not consumed, so the bytes can be reinterpreted.\n      wide <- Bytes(\"ffffffffffffffff\").reader()\n      stdout.println(`uint64 fits: ${wide.readUInt64BE()?}`)\n      stdout.println(`same bytes signed: ${wide.readInt64BE()}`)","migrationContext":"Java: ByteBuffer throws BufferUnderflowException on a short read, so untrusted input needs a try/catch or a hasRemaining() check per field; endianness is a mutable order() setting carried on the buffer, often set far from the read. C: reading past the end is undefined behaviour - nothing to catch, and the single most productive source of remote vulnerabilities in binary parsers. Python: struct.unpack raises while slicing silently returns short - two failure modes in one parse. Go: binary.Read returns an error per field, easy to assign and ignore. EK9: a short read is UNSET and does not move the cursor, so the ordinary and malformed paths are the same code; endianness is in the method name and cannot be defaulted; slice gives a hard boundary for nested structures.","keywords":["bigendian","binary","buffer","bytesreader","cursor","endian","littleendian","parse","protocol","slice","tls","underflow"],"primaryTopics":["BytesReader","binary parsing","endianness"],"typicalErrors":[],"companions":[]}
{"id":1385,"category":"Advanced Type System","question":"How do I build up binary data or write a network protocol message in EK9?","url":"https://ek9.io/qa/QA1385.html","alternatePhrasings":["Is there a StringBuilder equivalent for bytes in EK9?","How do I write a big-endian integer into bytes?","How do I XOR one buffer into another in EK9?","How do I erase a key or password buffer in EK9?"],"answer":"BytesBuilder is the mutable working buffer - the write side of the family, and the counterpart to BytesReader.\n\nWHY IT EXISTS (it is NOT just 'a faster Bytes')\nBytes is a value, so every + and | copies the whole accumulator; building byte-by-byte is quadratic. That matters for bulk work, but it is not the reason this type exists. Three things are inexpressible on Bytes at ANY speed:\n  1. ENDIAN-AWARE WRITES. BytesReader has readUInt32BE; without a builder there is no endian-aware write anywhere in the language, so you hand-assemble bytes and reintroduce the endianness bugs the reader eliminated.\n  2. IN-PLACE MUTATION - setByte, xorInto, copyInto. A value type cannot offer these; every operation on Bytes yields a new array.\n  3. WIPE. A Bytes CANNOT be securely erased, because + and | already scattered copies of it. A builder is one buffer you can actually zero.\n\nCREATING\n  builder <- BytesBuilder()               // empty, and SET - empty is not unset\n  sized <- BytesBuilder(1024)             // reserves capacity; length is still 0\n  seeded <- BytesBuilder(someBytes)       // initialise from an immutable value\nCapacity is backing storage, not content. reserve(n) grows it and never shrinks. Growth is geometric, so appending is amortised rather than quadratic.\n\nAPPENDING\n  builder.append(aByte) / append(someBytes) / append(anotherBuilder)\n  builder += aByte                        // and | through a stream terminal\nThe | and += spellings carry over from Bytes deliberately, so the familiar accumulation idiom reads the same - it is simply amortised here.\n\nTYPED WRITES MIRROR THE READER EXACTLY\n  appendUInt16BE/LE, appendUInt32BE/LE, appendUInt64BE/LE\n  appendInt16BE/LE,  appendInt32BE/LE,  appendInt64BE/LE\nTwelve writes against BytesReader's twelve reads. There is no appendUInt32 with a default, for the same reason there is no readUInt32: a silent endianness assumption is a bug category, so the defaulted spelling does not exist.\n\nRANGE IS ENFORCED, NOT TRUNCATED\nappendUInt16BE(0x10000) makes the builder UNSET rather than writing 0x0000. And appendUInt64BE(-1) is REFUSED rather than written as two's complement - the mirror image of readUInt64BE refusing to hand back a negative for a large positive. Use appendInt64BE when you mean a signed value.\n\nIN-PLACE MUTATION\n  builder.setByte(index, aByte)\n  builder.xorInto(offset, source)\n  builder.copyInto(offset, source)\nAll three write WITHIN the current length and never extend - use append to grow. A write that would run past the end is refused OUTRIGHT rather than part-applied, because half an applied round key is worse than none. xorInto is one primitive with three uses: AES AddRoundKey, the Argon2 block XOR and the ChaCha20 keystream application.\n\nSECURE HYGIENE\n  secret.wipe()\nZeroes the backing store and leaves the builder usable. Best-effort by nature - the JVM may have moved the buffer during GC and the OS may have paged it - but it is the only place erasure is even possible.\n\nCROSSING BACK\n  frozen <- builder.toBytes()\nThe single visible boundary between 'doing byte work' and handing a value to the rest of the program. It always COPIES, so a snapshot cannot change underneath its holder.\n\nAND $ SHOWS SHAPE, NOT CONTENT\n$builder gives BytesBuilder(4/16) - the length and capacity, never the bytes. A buffer holding a key must not leak into a log line through a debug print. For the same reason there is no #? - a hash that changes as you append makes a mutable key look usable, and that is a bug.\n\nSee Q1384 for BytesReader, Q1383 for Bytes and Q1382 for Byte. Use 'ek9 -h BytesBuilder' for the full API.","ek9Example":"#!ek9\ndefines module qa.bytesbuilder.usage\n\n  defines constant\n\n    tlsVersionTls12 <- 0x0303\n    tlsBodyLength <- 8\n\n  defines program\n\n    BytesBuilderUsage()\n      stdout <- Stdout()\n\n      //The TLS record Q1384 parses - endianness is in the method name.\n      record <- BytesBuilder()\n      record.append(Byte(\"16\"))\n      record.appendUInt16BE(tlsVersionTls12)\n      record.appendUInt16BE(tlsBodyLength)\n      record.append(Bytes(\"034142430358595a\"))\n\n      framed <- record.toBytes()\n      stdout.println(`framed: ${framed}`)\n\n      //It reads back through the reader.\n      cursor <- framed.reader()\n      stdout.println(`type: ${cursor.readByte()}`)\n      stdout.println(`version: ${cursor.readUInt16BE()}`)\n      stdout.println(`declared length: ${cursor.readUInt16BE()}`)\n\n      //BE and LE genuinely differ - no defaulted spelling exists.\n      bigEndian <- BytesBuilder()\n      bigEndian.appendUInt32BE(0x01020304)\n      littleEndian <- BytesBuilder()\n      littleEndian.appendUInt32LE(0x01020304)\n      stdout.println(`big endian: ${bigEndian.toBytes()}`)\n      stdout.println(`little endian: ${littleEndian.toBytes()}`)\n\n      //Too wide for the field is REJECTED, never truncated.\n      tooWide <- BytesBuilder()\n      tooWide.appendUInt16BE(0x10000)\n      stdout.println(`too wide accepted: ${tooWide?}`)\n\n      //Negative unsigned is refused; the signed spelling accepts it.\n      negativeUnsigned <- BytesBuilder()\n      negativeUnsigned.appendUInt64BE(-1)\n      stdout.println(`negative unsigned accepted: ${negativeUnsigned?}`)\n      negativeSigned <- BytesBuilder()\n      negativeSigned.appendInt64BE(-1)\n      stdout.println(`negative signed: ${negativeSigned.toBytes()}`)\n\n      //xorInto is AddRoundKey / Argon2 block XOR / ChaCha20 keystream, self-inverse.\n      block <- BytesBuilder(Bytes(\"ffffffff\"))\n      block.xorInto(0, Bytes(\"0f0f0f0f\"))\n      stdout.println(`after xor: ${block.toBytes()}`)\n      block.xorInto(0, Bytes(\"0f0f0f0f\"))\n      stdout.println(`xor undone: ${block.toBytes()}`)\n\n      //A write past the end is refused outright.\n      overrun <- BytesBuilder(Bytes(\"ffffffff\"))\n      overrun.xorInto(2, Bytes(\"0f0f0f\"))\n      stdout.println(`overrun accepted: ${overrun?}`)\n\n      //wipe zeroes the buffer - an immutable Bytes cannot be erased.\n      secret <- BytesBuilder(Bytes(\"cafebabedeadbeef\"))\n      secret.wipe()\n      stdout.println(`wiped length: ${length secret}`)\n      stdout.println(`still usable: ${secret?}`)\n\n      //toBytes always COPIES - a snapshot cannot move under its holder.\n      snapshotting <- BytesBuilder(Bytes(\"deadbeef\"))\n      snapshot <- snapshotting.toBytes()\n      snapshotting.append(Byte(\"FF\"))\n      stdout.println(`snapshot: ${snapshot}`)\n      stdout.println(`builder now: ${snapshotting.toBytes()}`)\n\n      //$ reports SHAPE, never CONTENT - a key must not reach a log line.\n      stdout.println(`shape only: ${$snapshotting}`)","migrationContext":"Java: ByteBuffer.putInt writes in whatever order the buffer's order() setting happens to be - decided elsewhere in the program - and silently truncates a long into an int field; a password in a String cannot be erased at all, which is why the JDK's own APIs take char[]. C: a memcpy of a struct writes host byte order and host padding, correct on the machine it was written on, and memset on a dead buffer may be optimised away entirely - which is why memset_s and explicit_bzero exist. Python: struct.pack raises on an out-of-range value, which is right, but the endianness prefix is easy to omit and native order is then used; bytes are immutable so their copies cannot be cleared. Go: binary.Write takes an explicit binary.BigEndian, but a too-wide value is handled by the conversion at the call site, so the truncation just moves. EK9 puts the endianness in the method name, makes an out-of-range write visibly absent instead of quietly plausible, and gives key material one buffer that can actually be zeroed.","keywords":["append","bigendian","binary","buffer","builder","bytesbuilder","crypto","endian","littleendian","mutable","wipe","xor"],"primaryTopics":["BytesBuilder","binary writing","mutable buffers"],"typicalErrors":[],"companions":[]}
{"id":1386,"category":"Advanced Type System","question":"How do I hash data in EK9, and what is the difference between Digest and HMAC?","url":"https://ek9.io/qa/QA1386.html","alternatePhrasings":["How do I compute a SHA-256 in EK9?","Which type do I use for hashing, Digest or HMAC?","How do I hash a request body or a file rather than a String?","Why does my digest not equal my Bytes hex?"],"answer":"Digest produces UNKEYED cryptographic hashes. It answers ONE question - has this content changed? - and proves INTEGRITY only. Anyone at all can compute a digest, so anyone who can replace your content can replace the digest beside it.\n\nTHREE OVERLOADS\n  Digest().SHA256(text)      // String -> String, UPPER-case hex\n  Digest().SHA256(aGuid)     // GUID   -> String, UPPER-case hex\n  Digest().SHA256(someBytes) // Bytes  -> Bytes, the OCTETS\n\nSHA512 HAS THE SAME THREE OVERLOADS\nDigest().SHA512(...) takes a String, a GUID or Bytes exactly as SHA256 does - one shape, two algorithms. Its Bytes form returns 64 octets where SHA-256 returns 32, and its hex is 128 characters.\n\nUSE THE BYTES OVERLOAD FOR ANYTHING THAT IS NOT TEXT\nA request body, a file, a frame off the wire - none of those have a guaranteed encoding, so routing them through a String needs an assumption you may not be entitled to make. SHA256(Bytes) hashes the octets with no decode at all, and the result chains straight into the next step (an HMAC key, a signature) with no hex round trip.\n\nHEX IS UPPER-CASE ACROSS THE FAMILY\nSHA256(String), Bytes' $ and toText(\"hex\") all emit UPPER-case, so a String digest and a Bytes digest of the same input compare EQUAL with no normalising. That was not true before 2026-09-25: the String form emitted lower-case, inherited from the type formerly misnamed HMAC, and the two spellings of one SHA-256 were silently unequal. ⚠️ Published vectors are conventionally WRITTEN lower-case (shasum, git and openssl all print that way), so a literal copied from a spec needs its case folded - the value is identical, only the rendering differs.\n\nTRI-STATE\nUnset in, unset out - never a digest over a substitute value. An EMPTY Bytes is SET, not unset, and hashing zero octets is well defined: it gives the published SHA-256 of empty input.\n\nIF YOU NEED TO PROVE WHO, THIS IS THE WRONG TYPE\nA hash over attacker-supplied content is exactly as valid as a hash over yours. To prove a message came from someone holding a secret, use HMAC - see Q1387. This split exists because the type named HMAC used to hold only these unkeyed hashes, so code reaching for it got a bare digest and the authenticity it assumed was imaginary.\n\nSHA-1 and MD5 are deliberately NOT on Digest - both are broken for security use. See Q1383 for Bytes and Q1387 for the keyed MAC. Use 'ek9 -h Digest' for the full API.","ek9Example":"#!ek9\ndefines module qa.digest.usage\n\n  defines constant\n\n    sampleText <- \"the quick brown fox\"\n\n  defines program\n\n    DigestUsage()\n      stdout <- Stdout()\n\n      //A digest answers ONE question: has this content changed? Anyone can compute it,\n      //so it proves integrity and nothing whatsoever about who produced it.\n      fingerprint <- Digest().SHA256(sampleText)\n      stdout.println(`fingerprint: ${fingerprint}`)\n      stdout.println(`length: ${length fingerprint}`)\n\n      //The SAME content always gives the SAME digest - that is the whole mechanism.\n      stdout.println(`stable: ${Digest().SHA256(sampleText) == fingerprint}`)\n\n      //One changed character changes the whole value. There is no \"nearly equal\" digest.\n      stdout.println(`avalanche: ${Digest().SHA256(\"the quick brown fix\") <> fingerprint}`)\n\n      //🔑 THE BYTES OVERLOAD takes and returns OCTETS. Use it for a request body, a file,\n      //or anything else that is not text: no decode, so no encoding to assume wrongly.\n      payload <- Bytes().withUtf8(sampleText)\n      asOctets <- Digest().SHA256(payload)\n      stdout.println(`octets length: ${length asOctets}`)\n\n      //🔑 Both spellings of a SHA-256 agree EXACTLY - hex is UPPER-case across the whole family,\n      //so a String digest and a Bytes digest of the same input compare equal with no normalise.\n      stdout.println(`same value: ${asOctets.toText(\"hex\") == fingerprint}`)\n\n      //Published vector: SHA-256(\"abc\"). Bytes(\"616263\") is that ASCII, written as hex.\n      stdout.println(`abc: ${Digest().SHA256(Bytes(\"616263\"))}`)\n\n      //SHA512 is the same shape: the same three overloads, 64 octets instead of 32.\n      longer <- Digest().SHA512(payload)\n      stdout.println(`sha512 octets length: ${length longer}`)\n      stdout.println(`sha512 same value: ${longer.toText(\"hex\") == Digest().SHA512(sampleText)}`)\n\n      //A GUID hashes through its text form - handy for opaque, stable identifiers.\n      stdout.println(`guid hashed: ${Digest().SHA256(GUID())?}`)\n\n      //Unset in, unset out - never a digest over a substitute value.\n      stdout.println(`unset in: ${Digest().SHA256(String())?}`)","migrationContext":"Java: MessageDigest.getInstance(\"SHA-256\") throws a checked NoSuchAlgorithmException for an algorithm the platform mandates, returns a mutable byte[] with reference equality, and leaves hex formatting to you. Python: hashlib.sha256(...).hexdigest() is convenient but silently encodes a str with the default codec, which is the encoding assumption EK9 refuses to make. Go: crypto/sha256 returns [32]byte and hex.EncodeToString is a separate step. Node: crypto.createHash('sha256').update(x).digest('hex') guesses an encoding for a string input. EK9: the algorithm cannot fail to exist, the input is either text you named or octets you supplied, the result is tri-state, and Bytes carries its own length with value equality.","keywords":["checksum","digest","etag","fingerprint","hash","hashing","integrity","octets","sha256","sha512","unkeyed"],"primaryTopics":["Digest","hashing","binary data"],"typicalErrors":[{"error":"E50060","correct":"fingerprint <- Digest().SHA256(sampleText)","incorrect":"fingerprint <- HMAC().SHA256(sampleText)","explanation":"HMAC is KEYED and takes SHA256(key, message) over Bytes. The unkeyed hashes live on Digest. The compiler names the only available signature in the error, so it tells you which of the two you actually reached."},{"error":"E07620","correct":"asOctets.toText(\"hex\") == fingerprint","incorrect":"asOctets == fingerprint","explanation":"SHA256(String) yields a String and SHA256(Bytes) yields Bytes - different types, so there is no == between them. Convert one side; both render UPPER-case hex, so once the types match the values compare equal."}],"companions":[]}
{"id":1387,"category":"Advanced Type System","question":"How do I compute an HMAC in EK9, and how do I verify one safely?","url":"https://ek9.io/qa/QA1387.html","alternatePhrasings":["How do I sign a request in EK9?","How do I authenticate a message with a shared secret?","How do I build an AWS SigV4 signing key?","Why should I not compare a MAC with ==?"],"answer":"HMAC is a KEYED message authentication code. It proves INTEGRITY AND AUTHENTICITY, because producing one requires the key:\n\n  mac <- HMAC().SHA256(key, message)   // both Bytes, result Bytes\n\nThat is the whole API. There is no constructor argument - the key is a parameter of SHA256, not state on the object.\n\nWHY OCTETS RATHER THAN STRING\nDerivation chains feed one MAC's OUTPUT in as the next one's KEY. AWS SigV4 is the canonical shape: secret -> date -> region -> service -> request. A String API would force a hex round trip at every link, and a MAC's output is not text in any encoding. Key and message are Bytes for that reason.\n\nVERIFY WITH constantTimeEquals, NEVER WITH ==\nBoth answer the same question, but == is free to stop at the first differing byte. How long it took then tells an attacker how many leading bytes they guessed correctly, which forges a tag one byte at a time. Bytes.constantTimeEquals folds in the XOR at EVERY position, and the length difference too, so there is nothing to time.\n\nTRI-STATE\nAn unset key or an unset message gives an unset result - never a MAC computed over a substitute value. An EMPTY key is a different thing from an unset one and is accepted, because empty is SET.\n\nTHIS TYPE USED TO BE MISNAMED\nUntil the Bytes family existed there was no honest way to express a keyed MAC, so this type carried the UNKEYED hashes instead - code reaching for 'HMAC' got a bare digest and the authenticity it assumed was imaginary. Those hashes now live on Digest (Q1386).\n\nThe example below uses RFC 4231 test case 2, a PUBLISHED vector, so it agrees with every conforming implementation or it is wrong. A value captured from our own output would pass equally happily over a subtly broken MAC. See Q1383 for Bytes. Use 'ek9 -h HMAC' for the full API.","ek9Example":"#!ek9\ndefines module qa.hmac.usage\n\n  defines program\n\n    HmacUsage()\n      stdout <- Stdout()\n\n      //An HMAC is KEYED. Producing one requires the secret, so it proves integrity AND\n      //authenticity - the thing a bare Digest cannot do, because anyone can recompute that.\n      //RFC 4231 test case 2: a PUBLISHED vector, so this agrees with every conforming\n      //implementation or it is wrong.\n      key <- Bytes().withUtf8(\"Jefe\")\n      message <- Bytes().withUtf8(\"what do ya want for nothing?\")\n      mac <- HMAC().SHA256(key, message)\n      stdout.println(`mac: ${mac}`)\n      stdout.println(`length: ${length mac}`)\n\n      //🔑 The key MUST participate. Change it and the whole value changes - which is exactly\n      //what an attacker cannot do without knowing it.\n      forged <- HMAC().SHA256(Bytes().withUtf8(\"not-the-key\"), message)\n      stdout.println(`key matters: ${mac <> forged}`)\n\n      //⚠️ VERIFY WITH constantTimeEquals, NEVER ==. Both answer the same question, but ==\n      //is free to stop at the first differing byte, and how long that took tells an attacker\n      //how many leading bytes they guessed right - a tag forged one byte at a time.\n      stdout.println(`verified: ${mac.constantTimeEquals(HMAC().SHA256(key, message))}`)\n      stdout.println(`rejected: ${mac.constantTimeEquals(forged)}`)\n\n      //🔑 KEY AND MESSAGE ARE OCTETS because derivation chains feed one MAC's OUTPUT in as the\n      //next one's KEY. AWS SigV4 is the canonical shape; a String API would force a hex\n      //round trip at every link.\n      dateKey <- HMAC().SHA256(Bytes().withUtf8(\"AWS4-secret\"), Bytes().withUtf8(\"20260924\"))\n      regionKey <- HMAC().SHA256(dateKey, Bytes().withUtf8(\"eu-west-2\"))\n      signingKey <- HMAC().SHA256(regionKey, Bytes().withUtf8(\"aws4_request\"))\n      stdout.println(`derived length: ${length signingKey}`)\n      stdout.println(`chain diverges: ${signingKey <> dateKey}`)\n\n      //Unset in, unset out - never a MAC computed over a substitute value.\n      stdout.println(`unset key: ${HMAC().SHA256(Bytes(), message)?}`)\n\n      //An EMPTY key is a different thing from an unset one, and is accepted - empty is SET.\n      stdout.println(`empty key: ${HMAC().SHA256(Bytes().withUtf8(\"\"), message)?}`)","migrationContext":"Java: Mac.getInstance(\"HmacSHA256\") plus a SecretKeySpec, two checked exceptions for conditions that cannot arise, a mutable byte[] out, and MessageDigest.isEqual for the constant-time compare that nobody remembers to use. Python: hmac.new(key, msg, hashlib.sha256).hexdigest() with hmac.compare_digest as a separate opt-in. Go: hmac.New(sha256.New, key) then hmac.Equal - the standard library is explicit that == is wrong here. Node: crypto.createHmac plus crypto.timingSafeEqual, which throws if the lengths differ. EK9: one method, both arguments tri-state Bytes, and constantTimeEquals sits on Bytes itself so the safe comparison is the one already to hand.","keywords":["authentication","constanttime","hmac","keyed","mac","secret","signing","sigv4","timing","verify"],"primaryTopics":["HMAC","cryptography","binary data"],"typicalErrors":[{"error":"E50060","correct":"mac <- HMAC().SHA256(key, message)","incorrect":"mac <- HMAC(key, \"SHA256\")","explanation":"HMAC takes no constructor arguments and names no algorithm - SHA256 IS the method. The key is a parameter of that method, which is what lets one object serve many keys."},{"error":"E07620","correct":"mac.constantTimeEquals(HMAC().SHA256(key, message))","incorrect":"mac.toText(\"hex\") == HMAC().SHA256(key, message)","explanation":"This is the error you get when the received tag is a String of hex rather than Bytes - and it is worth fixing in the right direction. Parse the tag into Bytes and compare with constantTimeEquals; == is free to short-circuit and leaks how many leading bytes matched."}],"companions":[]}
{"id":1388,"category":"Advanced Type System","question":"How do I generate a secure random token, nonce or salt in EK9?","url":"https://ek9.io/qa/QA1388.html","alternatePhrasings":["What is the EK9 equivalent of java.security.SecureRandom?","How do I generate cryptographically secure random bytes?","Why can I not pass a SeededRandom where a SecureRandom is expected?","How do I make a session token or an API key?"],"answer":"SecureRandom produces cryptographically secure random OCTETS. It has exactly one operation:\n\n  salt <- SecureRandom().nextBytes(16)   // Integer count -> Bytes\n\nA TOKEN IS OCTETS RENDERED AS TEXT\n  token <- SecureRandom().nextBytes(32).toBase64()\n  hexId <- SecureRandom().nextBytes(16).toText(\"hex\")\nThere is deliberately no next() returning an Integer. Reducing random numbers into a range by hand is how modulo bias creeps into tokens, and octets rendered as Base64 or hex are what every protocol actually wants.\n\nIT IS DELIBERATELY NOT A Random\nSystemRandom and SeededRandom both implement the Random trait. SecureRandom does NOT. If it did, any function taking a Random would accept a SeededRandom just as happily - and a seeded generator produces the SAME sequence every run, so a nonce or salt made from it is reproducible by anyone who knows the seed. Keeping SecureRandom outside the trait means a function that needs unpredictable octets declares a SecureRandom parameter, and passing it a SeededRandom is a COMPILE ERROR (E06270), not a code-review catch.\n\nNO SEED, ON PURPOSE\nThere is no SecureRandom(seed) constructor. The generator seeds itself from the operating system's entropy source; there is no way to make it reproducible, and that is the whole point.\n\nTRI-STATE\nAn unset or negative count gives UNSET Bytes - never a substitute length. A count of 0 gives EMPTY Bytes, which are SET: asking for nothing and getting nothing is a well-defined answer.\n\nTESTING CODE THAT USES IT\nThere are no test vectors for a secure generator, by definition. Test the PROPERTIES instead: the requested length comes back, successive draws differ, two generators differ. If a test needs repeatable values, the code under test wanted a Random, not a SecureRandom - the two answer different questions.\n\nSee Q1383 for Bytes, Q1386 for Digest and Q1387 for HMAC. Use 'ek9 -h SecureRandom' for the full API.","ek9Example":"#!ek9\ndefines module qa.securerandom.usage\n\n  defines function\n\n    //Declaring SecureRandom here is the safety property: a SeededRandom will not compile at the call site.\n    newSalt() as pure\n      ->\n        generator as SecureRandom\n      <-\n        rtn as Bytes: generator.nextBytes(16)\n\n  defines program\n\n    SecureRandomUsage()\n      stdout <- Stdout()\n      generator <- SecureRandom()\n\n      //A salt: exactly the number of octets asked for.\n      salt <- newSalt(generator)\n      stdout.println(`salt length: ${length salt}`)\n\n      //A token is octets rendered as text - 32 octets is 44 characters of Base64.\n      token <- generator.nextBytes(32).toBase64()\n      stdout.println(`token length: ${length token}`)\n\n      //Or as hex - two characters per octet.\n      hexId <- generator.nextBytes(16).toText(\"hex\")\n      stdout.println(`hex id length: ${length hexId}`)\n\n      //Successive draws differ, and so do two generators - there is no seed for them to share.\n      stdout.println(`draws differ: ${generator.nextBytes(32) <> generator.nextBytes(32)}`)\n      stdout.println(`generators differ: ${SecureRandom().nextBytes(32) <> SecureRandom().nextBytes(32)}`)\n\n      //Asking for nothing gives EMPTY Bytes - which are SET. Empty is not unset.\n      nothing <- generator.nextBytes(0)\n      stdout.println(`empty is set: ${nothing?}`)\n      stdout.println(`empty length: ${length nothing}`)\n\n      //An unset or negative count gives UNSET - never a substitute length.\n      stdout.println(`unset count: ${generator.nextBytes(Integer())?}`)\n      stdout.println(`negative count: ${generator.nextBytes(-1)?}`)","migrationContext":"Java: java.security.SecureRandom EXTENDS java.util.Random, so any method taking a Random accepts either, and new Random(42) passed where a SecureRandom was intended type-checks and yields the same nonce every run. Python: secrets.token_bytes / token_urlsafe are right, but random.getrandbits looks identical at the point of use and nothing in a signature distinguishes them. Go: crypto/rand.Read and math/rand.Read share a shape, so an io.Reader parameter accepts either. Node: crypto.randomBytes is correct; Math.random is not cryptographic and is the one people reach for. EK9: a separate type outside the Random trait, octets only, no seed, and the wrong generator is a compile error.","keywords":["cryptography","csprng","entropy","nonce","random","salt","securerandom","seededrandom","token","unpredictable"],"primaryTopics":["SecureRandom","cryptography","binary data"],"typicalErrors":[{"error":"E06270","correct":"salt <- newSalt(generator)","incorrect":"salt <- newSalt(SeededRandom(42))","explanation":"SecureRandom is deliberately NOT a Random, so no Random - seeded or otherwise - can stand in for it. That is the point: a seeded generator gives the same salt every run. The compiler rejects the substitution at the call site."},{"error":"E50060","correct":"token <- generator.nextBytes(32).toBase64()","incorrect":"token <- generator.next()","explanation":"SecureRandom only produces octets. There is no next() returning an Integer; render the octets as Base64 or hex for a token, or use SystemRandom if you genuinely wanted a non-cryptographic number."},{"error":"E50060","correct":"generator <- SecureRandom()","incorrect":"generator <- SecureRandom(42)","explanation":"There is no seeded constructor - a secure generator seeds itself from the operating system and cannot be made reproducible. If you need repeatable values (a simulation, a test), you wanted SeededRandom."}],"companions":[]}
{"id":1389,"category":"Variable Naming Rules and Conventions","question":"Can I name my own class, record, enumeration or function after a built-in type such as Colour, Money or Date?","url":"https://ek9.io/qa/QA1389.html","alternatePhrasings":["What is E01090 in EK9?","Why does EK9 reject a class called Date?","Why can't I define an enumeration called Colour in EK9?","What does 'is the name of a built-in construct' mean?"],"answer":"No. No construct of any kind - class, record, trait, component, function, enumeration or other type, text, service, application, program or constant - may take the name of a built-in construct. The compiler rejects it with E01090.\n\nWHY\nThe built-in module org.ek9.lang is visible in every source file without being imported. A user class called Date would give 'Date' two meanings in one program: which one a line means would depend on where you are reading. That confuses a person, and it confuses an AI generating or reviewing code even more. EK9 removes the second meaning instead of asking you to keep track of it.\n\nHOW IT IS CHECKED\nNo list of names is involved. Before a construct is defined, the compiler resolves its name; if that finds a built-in construct, E01090 is reported. Every current and future built-in is covered.\n\nWHAT TO DO\nChoose a name that says what YOUR construct is: an enumeration of colours becomes Hue (or TrafficLight, Paint...), a record of money becomes Amount or Price, your own Date becomes AppointmentDate. The built-in types remain available and unambiguous alongside yours - this program uses the built-in Money literal next to its own Amount.\n\nVariables are not affected: a variable called colour is fine. See also E01091 (type parameter names) and E01089 (names differing only by case).","ek9Example":"#!ek9\ndefines module qa.naming.builtinnames\n\n  defines type\n\n    Hue\n      Red\n      Green\n      Blue\n\n  defines class\n\n    Amount\n      pence <- Integer()\n      code <- String()\n\n      Amount()\n        ->\n          pence as Integer\n          code as String\n        this.pence: pence\n        this.code: code\n\n      describe()\n        <- rtn as String: `${pence} ${code}`\n\n      default operator\n\n  defines program\n\n    BuiltInNamesDemo()\n      stdout <- Stdout()\n\n      shade <- Hue.Green\n      stdout.println(`Hue: ${shade}`)\n\n      price <- Amount(250, \"GBP\")\n      stdout.println(`Amount: ${price.describe()}`)\n\n      //The built-in Money is still there, alongside our own Amount - and unambiguous\n      paid <- 2.50#GBP\n      stdout.println(`Money: ${paid}`)","migrationContext":"Java: a class named Date in your package silently shadows java.util.Date for unqualified use in that package, and which one an unqualified 'Date' means depends on the imports. Python: assigning 'list = ...' or defining 'class str' shadows the builtin in that scope. C#/Kotlin: a user type can hide a framework type of the same name. EK9: built-in names are reserved for the built-ins - one name, one meaning.","keywords":["Colour","Date","E01090","Money","built-in","conflict","construct","name","naming","shadow"],"primaryTopics":[],"typicalErrors":[{"error":"E01090","correct":"    Hue\n      Red\n      Green\n      Blue","incorrect":"    Colour\n      Red\n      Green\n      Blue","explanation":"Colour is a built-in type (the #RRGGBB colour literal type). Name the enumeration for what it is. See ek9 -h E01090."},{"error":"E01090","correct":"    Amount\n      pence <- Integer()","incorrect":"    Money\n      pence <- Integer()","explanation":"Money is a built-in type. Your own money-like construct needs its own name, e.g. Amount or Price. See ek9 -h E01090."},{"error":"E01090","correct":"    BuiltInNamesDemo()","incorrect":"    Stdout()","explanation":"Programs, functions and constants are constructs too - Stdout is the built-in standard output type. See ek9 -h E01090."}],"companions":[]}
{"id":1390,"category":"Variable Naming Rules and Conventions","question":"What can I name the type parameters of a generic type or function?","url":"https://ek9.io/qa/QA1390.html","alternatePhrasings":["What is E01091 in EK9?","Why can't a generic function be declared 'of type Integer'?","Why does EK9 reject a type parameter that names an existing type?","What does 'a type parameter name must not resolve to anything' mean?"],"answer":"A type parameter is a placeholder, so its name must not resolve to anything that already exists - not a built-in type, not one of your own classes or functions (even one declared later in the file), not a constant. Use a conceptual name: T, K, V, or a descriptive one such as Element or Key. A name that does resolve is rejected with E01091.\n\nWHY\n'twice() of type Integer' with '-> amount as Integer' would otherwise compile - and 'twice() of String' then takes and returns a String, while every declaration in the function body reads 'Integer'. The compiler knows 'Integer' there means the parameter; a reader, or an AI, reads the built-in Integer and draws the wrong conclusions about overflow, arithmetic and what the function accepts.\n\nHOW IT IS CHECKED\nThe name is resolved from the MODULE scope once every construct of the program is known, so a class declared further down the file is still found. A name brought in with 'references' is rejected even earlier, as E03010.\n\nThis program shows the correct form: 'twice() of type T' reads as a placeholder, and the same function then works for Integer (21 + 21 = 42) and for String (\"ha\" + \"ha\" = \"haha\").","ek9Example":"#!ek9\ndefines module qa.naming.typeparameters\n\n  defines function\n\n    twice() of type T\n      -> amount as T\n      <- rtn as T: amount + amount\n\n  //Declared AFTER twice() - a type parameter still may not take its name (E01091).\n  defines class\n\n    Widget\n      label <- String()\n\n      default operator ?\n\n  defines program\n\n    TypeParameterNamesDemo()\n      stdout <- Stdout()\n\n      numbers <- twice() of Integer\n      stdout.println($numbers(21))\n\n      words <- twice() of String\n      stdout.println(words(\"ha\"))","migrationContext":"Java: 'class Box<String>' is legal and declares a type variable named String that shadows java.lang.String inside the class - a known puzzler. C#, Kotlin and TypeScript allow the same. EK9 rejects a type parameter name that resolves to anything.","keywords":["E01091","T","conceptual type","generic","naming","of type","placeholder","shadow","type parameter"],"primaryTopics":["type parameter names","E01091","generic placeholder name"],"typicalErrors":[{"error":"E01091","correct":"    twice() of type T\n      -> amount as T\n      <- rtn as T: amount + amount","incorrect":"    twice() of type Integer\n      -> amount as Integer\n      <- rtn as Integer: amount + amount","explanation":"Integer resolves to the built-in type, so as a type parameter name it gives 'Integer' a second meaning inside the function. Use a placeholder such as T. See ek9 -h E01091."},{"error":"E01091","correct":"    twice() of type T\n      -> amount as T\n      <- rtn as T: amount + amount","incorrect":"    twice() of type Widget\n      -> amount as Widget\n      <- rtn as Widget: amount + amount","explanation":"Widget is a class in this module, even if declared later in the file - a type parameter may not reuse its name. See ek9 -h E01091."}],"companions":[]}
{"id":1391,"category":"Advanced Type System","question":"Why is 'Ratio as Float constrain as >= 1' rejected with E08265?","url":"https://ek9.io/qa/QA1391.html","alternatePhrasings":["Why must a Float constraint use 1.0 instead of 1?","Does EK9 promote an Integer literal inside a constraint?","How do I constrain a user-defined type with whole-number bounds?","Why can't a String constraint compare with a Character like 'x'?"],"answer":"A constraint's literal must match the parameter type of the operator it is compared with EXACTLY. A constraint is never converted - there is no promotion inside one.\n\nTHE RULE\n  Ratio as Float constrain as\n    >= 1 and <= 100          // E08265: Float's '>=' takes a Float, not an Integer\n  Ratio as Float constrain as\n    >= 1.0 and <= 100.0      // correct\nThe same holds for every comparison operator and every promotable pair: a Character does not become a String either, so a String constraint writes <> \"x\", not <> 'x'.\n\nWHY A DECLARATION IS EXACT\nIn an ordinary expression, ratio >= 1 promotes the Integer to a Float and works. A constraint is different: it is a DECLARATION of the type's admissible values, and the check the compiler generates compares the value with the literal exactly as written. Before E08265 existed, '>= 1' on a Float compiled and then called a Float comparison taking an Integer that does not exist - it failed at the FIRST construction, on the JVM and natively. Rather than hide a conversion inside a declaration, EK9 makes the declaration say exactly what it means.\n\nTWO REMEDIES\n1. Built-in base (Float, String, ...): you cannot add operators to a built-in type, so write the literal in the operator's type - 1 becomes 1.0, 'x' becomes \"x\". The error message gives the respelling.\n2. User-defined base: you have a choice. Either write the literal in the type the existing operator takes, or ADD the operator that takes the literal's type. The example below gives Price both 'operator >= -> threshold as Float' and 'operator >= -> threshold as Integer', so its constraint can use whole-number bounds directly.\n\nWHAT IS NOT AFFECTED\nComparing a constrained VALUE with a base value at run time (age >= limit) is an ordinary expression and follows the ordinary rules - see Q717. Only the literal written inside 'constrain as' must match exactly.\n\nSee Q257 for constrained types and Q1376 for constraints that no value can satisfy. Use 'ek9 -h E08265' for the full error explanation.","ek9Example":"#!ek9\ndefines module qa.constraint.literal.exact\n\n  defines record\n\n    //A user-defined base. It declares '>=' and '<=' for Integer as well as Float, so a constraint\n    //written with whole-number bounds finds an operator taking exactly that type.\n    Price\n      amount as Float?\n\n      Price()\n        amount: Float()\n\n      Price()\n        -> initial as Float\n        amount: initial\n\n      Price()\n        -> other as Price\n        amount: other.amount\n\n      operator >= as pure\n        -> threshold as Float\n        <- rtn as Boolean: amount >= threshold\n\n      operator >= as pure\n        -> threshold as Integer\n        <- rtn as Boolean: amount >= #^threshold\n\n      operator <= as pure\n        -> threshold as Float\n        <- rtn as Boolean: amount <= threshold\n\n      operator <= as pure\n        -> threshold as Integer\n        <- rtn as Boolean: amount <= #^threshold\n\n      override operator ? as pure\n        <- rtn as Boolean: amount?\n\n      operator :=:\n        -> other as Price\n        amount :=: other.amount\n\n  defines type\n\n    //Built-in base: the bounds are written as Floats because Float's operators take a Float.\n    Ratio as Float constrain as\n      >= 1.0 and <= 100.0\n\n    //Built-in base: a String's '<>' takes a String, so the bound is \"x\", not the Character 'x'.\n    NotX as String constrain as\n      <> \"x\"\n\n    //User-defined base: Integer bounds work because Price declares the Integer operators.\n    Bounded as Price constrain as\n      >= 1 and <= 100\n\n  defines program\n\n    ConstraintLiteralExactType()\n      stdout <- Stdout()\n\n      stdout.println(`ratio 50.5 accepted: ${Ratio().of(50.5)?}`)\n      stdout.println(`ratio 0.5 accepted: ${Ratio().of(0.5)?}`)\n\n      stdout.println(`\"y\" accepted: ${NotX().of(\"y\")?}`)\n      stdout.println(`\"x\" accepted: ${NotX().of(\"x\")?}`)\n\n      stdout.println(`price 20.0 accepted: ${Bounded().of(Price(20.0))?}`)\n      stdout.println(`price 250.0 accepted: ${Bounded().of(Price(250.0))?}`)","migrationContext":"Java Bean Validation annotations such as @Min(1) and @DecimalMin(\"1\") accept a bound in whatever form the annotation takes and convert it silently; a hand-written check widens int to double invisibly. Kotlin and C# likewise widen numeric literals in comparisons. EK9 applies ordinary promotion in expressions, but a constrained type's declaration must state its bounds in the compared type, so the declared constraint and the checked constraint are the same thing.","keywords":["E08265","coercion","constrain","constraint","exact","float","integer","literal","operator","promotion"],"primaryTopics":["constrained types","constraints","promotion"],"typicalErrors":[{"error":"E08265","correct":"    Ratio as Float constrain as\n      >= 1.0 and <= 100.0","incorrect":"    Ratio as Float constrain as\n      >= 1 and <= 100","explanation":"Float's comparison operators take a Float. A constraint never promotes its literal, so an Integer bound is rejected; write it as a Float."},{"error":"E08265","correct":"    NotX as String constrain as\n      <> \"x\"","incorrect":"    NotX as String constrain as\n      <> 'x'","explanation":"'x' is a Character and String's '<>' takes a String. A Character is not converted inside a constraint; write the bound as a String literal."},{"error":"E08265","correct":"      operator >= as pure\n        -> threshold as Integer\n        <- rtn as Boolean: amount >= #^threshold","incorrect":"      //no 'operator >= -> threshold as Integer' - so Bounded's '>= 1' only reaches the Float operator by promotion","explanation":"For a user-defined base you may add the operator that takes the literal's type - or write the bound as a Float to use the existing operator."}],"companions":[]}
{"id":1392,"category":"Variable Naming Rules and Conventions","question":"Why can't a variable, parameter or named capture have the same name as a function or type?","url":"https://ek9.io/qa/QA1392.html","alternatePhrasings":["Why does '(discount: 0.25)' in a dynamic function give E01030?","Can a closure capture have the same name as a function?","Why is my captured variable name rejected in a dynamic function or class?","What is E01030 when naming a variable or capture in EK9?"],"answer":"In EK9 a name has one meaning in a scope. A variable - a local, a parameter, a property, a loop variable, a returning variable or a NAMED capture - may not reuse the name of a function (E01030) or a type, your own or a built-in one (E01040), or of anything the module imports with 'references' (E03010). Rename the variable; the function or type keeps its name.\n\nNAMED CAPTURES\nA dynamic function or class captures values in parentheses. '(price)' captures the local 'price' under its own name - that name was already checked where 'price' was declared. '(seasonalRate: 0.25)' introduces a NEW variable: it becomes a property of the dynamic function or class, so it obeys exactly the same rule. With a module function 'discount()' in scope, '(discount: 0.25)' is E01030 on the capture: inside the body a bare 'discount' would have two meanings, and a reader - or an AI - cannot tell whether the body calls the function or reads the capture.\n\nWHAT IS ALLOWED\nA named capture may reuse the name of an enclosing LOCAL: '(price: price * 2.0)'. Once captured, the enclosing local is not visible inside the body at all, so there is only one 'price' there.\n\nWithin one set of parentheses either every capture is named or none is (E06240). This program captures a rate and the price under descriptive names that do not clash with the discount() function, then re-captures 'price' with a new value.","ek9Example":"#!ek9\ndefines module qa.naming.captures\n\n  //Referenced explicitly - so no variable here may be named Stdout (E03010).\n  references\n    org.ek9.lang::Stdout\n\n  defines function\n\n    discount() as pure\n      <- rtn as Float: 0.1\n\n  defines program\n\n    CaptureNamingDemo()\n      stdout <- Stdout()\n      price <- 200.0\n\n      withSeasonalRate <- (seasonalRate: 0.25, basePrice: price) is Supplier of Float as pure function\n        r: basePrice * (1.0 - seasonalRate)\n      stdout.println($ withSeasonalRate())\n\n      withStandardRate <- (price: price * 2.0) is Supplier of Float as pure function\n        r: price * (1.0 - discount())\n      stdout.println($ withStandardRate())","migrationContext":"Java lambdas cannot rename a captured variable at all, and a local may shadow a method name freely. Kotlin, Swift and JavaScript closures capture by name and allow a local to shadow a function of the same name. EK9 allows neither shadowing: a variable - including a named capture - may not reuse a function or type name.","keywords":["E01030","E01040","E03010","capture","closure","dynamic class","dynamic function","named capture","naming","shadow"],"primaryTopics":["E01030","E01040"],"typicalErrors":[{"error":"E01030","correct":"(seasonalRate: 0.25, basePrice: price) is Supplier of Float as pure function\n        r: basePrice * (1.0 - seasonalRate)","incorrect":"(discount: 0.25, basePrice: price) is Supplier of Float as pure function\n        r: basePrice * (1.0 - discount)","explanation":"discount() is a function in this module, so a capture named 'discount' would give the name two meanings inside the body. Rename the capture. See ek9 -h E01030."},{"error":"E01030","correct":"price <- 200.0","incorrect":"discount <- 200.0","explanation":"A local variable may not reuse the name of a function either - the same rule as for a capture. See ek9 -h E01030."},{"error":"E01040","correct":"basePrice: price)","incorrect":"String: price)","explanation":"String is a built-in type, so a variable - here a named capture - may not be called String. The same applies to your own types. See ek9 -h E01040."},{"error":"E03010","correct":"seasonalRate: 0.25,","incorrect":"Stdout: 0.25,","explanation":"This module imports Stdout with 'references', so no variable - including a named capture - may take that name. See ek9 -h E03010."}],"companions":[]}
{"id":1393,"category":"Code Quality","question":"How many operators can one EK9 expression have, and how deeply can code nest?","url":"https://ek9.io/qa/QA1393.html","alternatePhrasings":["What does '21 operators in one expression - the limit is 20' mean?","Why is my long formula rejected with E11013?","What are EK9's hard limits on expressions, brackets and blocks?","How many chained method calls can one expression have?"],"answer":"EK9 bounds the shape of source as it is read, before it is parsed: one expression may hold at most 20 operators and at most 20 member accesses ('.'), both reported as E11013, and brackets ('(', '[' and '{' together) may nest at most 10 deep and blocks at most 20, both E11011. An expression ends at a comma or at the end of its statement; a line ending in a binary operator carries it on. Real code stays well inside: at most 15 operators and 7 member accesses in an expression. Coalescing chains are held tighter still by the expression-complexity score: at most 3 of ?? and ?:, or 2 of <? >? <=? >=?.\n\nThe fix is always to name a part: extract an intermediate variable (or a function) and use the name. Each limit is reported once, before the parser runs, and nothing else in that file is reported until it is fixed.\n\nSee Q1302 for coalescing complexity and Q814 for control-flow nesting.","ek9Example":"#!ek9\ndefines module qa.quality.expression.limits\n\n  defines constant\n    isbnLength <- 10\n\n  defines function\n\n    //THE FIX: as one expression this check would hold 23 operators - past the limit of 20 (E11013).\n    //Naming the weighted sum keeps each expression short, and says what the arithmetic is for.\n    isbnTenIsValid() as pure\n      -> digits as List of Integer\n      <- valid as Boolean?\n      weightedSum <- digits.getOrDefault(0, 0) * 10 + digits.getOrDefault(1, 0) * 9 + digits.getOrDefault(2, 0) * 8 + digits.getOrDefault(3, 0) * 7 + digits.getOrDefault(4, 0) * 6 + digits.getOrDefault(5, 0) * 5 + digits.getOrDefault(6, 0) * 4 + digits.getOrDefault(7, 0) * 3 + digits.getOrDefault(8, 0) * 2 + digits.getOrDefault(9, 0)\n      valid: length digits == isbnLength and weightedSum mod 11 == 0\n\n    //THE FIX: as one expression these brackets would nest 11 deep - past the limit of 10 (E11011).\n    //Naming the inner part splits the nesting across two expressions.\n    scaled() as pure\n      -> amount as Integer\n      <- result as Integer?\n      inner <- ((((amount + 1) * 2) + 3) * 4)\n      result: ((((((inner + 5) * 6) + 7) * 8) + 9) * 10)\n\n  defines program\n\n    ExpressionLimitsDemo()\n      stdout <- Stdout()\n      //0-306-40615-2\n      digits <- [0, 3, 0, 6, 4, 0, 6, 1, 5, 2]\n      stdout.println(`Valid: ${isbnTenIsValid(digits)}`)\n      stdout.println(`Scaled: ${scaled(1)}`)","migrationContext":"Java: javac accepts any expression length; very long expressions eventually fail as 'code too large' or a StackOverflowError in the compiler. C: the standard only guarantees 63 nesting levels. Python: the parser limits nesting to 200. SonarQube S1067 flags more than 3 boolean operators, as advice. EK9: a hard limit at 20 operators, checked before parsing.","keywords":["E11011","E11013","brackets","chain","expression","intermediate","limit","nesting","operators","quality"],"primaryTopics":["expression limits","E11013","E11011","intermediate variables"],"typicalErrors":[{"error":"E11013","correct":"      weightedSum <- digits.getOrDefault(0, 0) * 10 + digits.getOrDefault(1, 0) * 9 + digits.getOrDefault(2, 0) * 8 + digits.getOrDefault(3, 0) * 7 + digits.getOrDefault(4, 0) * 6 + digits.getOrDefault(5, 0) * 5 + digits.getOrDefault(6, 0) * 4 + digits.getOrDefault(7, 0) * 3 + digits.getOrDefault(8, 0) * 2 + digits.getOrDefault(9, 0)\n      valid: length digits == isbnLength and weightedSum mod 11 == 0","incorrect":"      valid: length digits == isbnLength and (digits.getOrDefault(0, 0) * 10 + digits.getOrDefault(1, 0) * 9 + digits.getOrDefault(2, 0) * 8 + digits.getOrDefault(3, 0) * 7 + digits.getOrDefault(4, 0) * 6 + digits.getOrDefault(5, 0) * 5 + digits.getOrDefault(6, 0) * 4 + digits.getOrDefault(7, 0) * 3 + digits.getOrDefault(8, 0) * 2 + digits.getOrDefault(9, 0)) mod 11 == 0","explanation":"As one expression the check holds 23 operators: length, ==, and, nine *, nine +, mod and ==. One expression may hold at most 20, so it is rejected as it is read. Naming the weighted sum leaves 18 operators in one expression and 5 in the other, and the result is identical."},{"error":"E11011","correct":"      inner <- ((((amount + 1) * 2) + 3) * 4)\n      result: ((((((inner + 5) * 6) + 7) * 8) + 9) * 10)","incorrect":"result <- (((((((((((amount + 1) * 2) + 3) * 4) + 5) * 6) + 7) * 8) + 9) * 10) + 11)","explanation":"Brackets nest 11 deep; the limit is 10, counting '(', '[' and '{' together. Naming the inner part splits the nesting across two expressions, neither deeper than 6."}],"companions":[]}
{"id":1394,"category":"Constants and Immutability","question":"Why must a constant's String be written with double quotes, and why is \"C:\\users\" an error?","url":"https://ek9.io/qa/QA1394.html","alternatePhrasings":["What does E07902 'a plain \"...\" String literal is required here' mean?","Can a constant use a backtick String?","What does E07903 'is not a unicode escape' mean?","How do I write a backslash followed by a u in an EK9 String?"],"answer":"A constant, a constraint, a text's language (defines text for \"de\") and a service parameter's name (HEADER \"X-Token\") are all read at compile time, so each must be a plain \"...\" literal. A backtick String - interpolated or not - is not a compile-time literal, and is E07902. (Interpolation belongs in a function or a local variable, where it is evaluated.)\n\nIn any String, a backslash-u starts a unicode escape and must be followed by exactly four hex digits (0-9, a-f, A-F): \"\\u00e9\" is an e with an acute accent. Anything else - \"\\u+041\", a too-short \"\\u004\", or the 'u' of a Windows path like \"C:\\users\" - is E07903. To write a backslash followed by a 'u', escape the backslash: \"C:\\\\users\".\n\nSee Q43 for escaping in interpolated Strings and Q909 for $ in backtick Strings.","ek9Example":"#!ek9\ndefines module qa.constants.plain.literals\n\n  defines constant\n\n    GREETING <- \"Welcome\"\n\n  defines function\n\n    backslashPath() as pure\n      <- rtn as String: \"C:\\\\users\\\\steve\"\n\n  defines program\n\n    PlainLiteralsDemo()\n      stdout <- Stdout()\n      stdout.println(GREETING)\n      stdout.println(backslashPath())","migrationContext":"Java: a malformed \\u in source is an 'illegal unicode escape' compile error - even in a comment. Kotlin: const val needs a compile-time constant, but string templates are allowed and folded. Python and JavaScript: no compile-time constants. EK9: constants are literals, written only as \"...\", and a malformed escape is named for what it is.","keywords":["E07902","E07903","String","backslash","backtick","constant","double quote","escape","literal","unicode"],"primaryTopics":["constants","String literals","E07902","E07903"],"typicalErrors":[{"error":"E07902","correct":"    GREETING <- \"Welcome\"","incorrect":"    GREETING <- `Welcome`","explanation":"A constant is read at compile time, and a backtick String is not a compile-time literal - interpolated or not. Before E07902 its raw source text, backticks included, became the constant's value. Write the constant with double quotes; build interpolated text in a function or a local variable instead."},{"error":"E07903","correct":"      <- rtn as String: \"C:\\\\users\\\\steve\"","incorrect":"      <- rtn as String: \"C:\\users\\steve\"","explanation":"A backslash-u must be followed by exactly four hex digits; 'sers' is not, so the String is rejected rather than guessed at. Escape the backslash - two backslashes - to write a backslash followed by a plain 'u'."}],"companions":[]}
{"id":1395,"category":"Collections and Data Structures","question":"Why is 'animals as List of Animal: [ Dog() ]' a List of Animal when '[ Dog() ]' on its own is a List of Dog?","url":"https://ek9.io/qa/QA1395.html","alternatePhrasings":["How is the type of a list literal decided in EK9?","Can a List of Animal be initialised with only Dog elements?","Why can't I assign my List of Dog variable to a List of Animal?","Does a Dict literal take its key and value types from the declaration?"],"answer":"A List or Dict literal gets its type in one of two ways.\n\nINFERRED - NO DECLARED TYPE\n'pets <- [ Dog(), Cat() ]' works out the most specific type every element shares: with Dog and Cat both extending Animal it is a List of Animal. '[ Dog() ]' alone is a List of Dog - that is all the compiler has to go on.\n\nDECLARED - YOU SAID WHICH TYPE\n'animals as List of Animal: [ Dog() ]' is ALWAYS a List of Animal, whatever the elements happen to be, because you were explicit. Each element must then be assignable to Animal exactly as a single assignment would be; one that is not - a Rock - is E50030, reported at that element. The same holds for a plain assignment ('animals := [ Cat() ]'), a returning declaration ('<- rtn as List of Animal: [ Dog() ]'), a nested literal ('List of List of Animal: [ [ Dog() ] ]') and a Dict literal, whose keys and values are checked against the declared key and value types.\n\nAN EXISTING LIST STAYS WHAT IT IS\n'dogs <- [ Dog() ]' followed by 'animals as List of Animal: dogs' is E50030. That is not a literal but a second name for the same mutable list: through 'animals' you could add a Cat to something 'dogs' still promises holds only Dogs. Generic types are invariant for exactly this reason.","ek9Example":"#!ek9\ndefines module qa.collections.declaredliteraltype\n\n  defines class\n\n    Animal as open\n      name <- \"animal\"\n\n      describe()\n        <- rtn as String: \"some animal\"\n\n      default operator ?\n\n    Dog extends Animal\n      override describe()\n        <- rtn as String: \"dog\"\n\n      default operator ?\n\n    Cat extends Animal\n      override describe()\n        <- rtn as String: \"cat\"\n\n      default operator ?\n\n    Rock\n      weight <- 1\n\n      default operator ?\n\n  defines program\n\n    DeclaredLiteralTypeDemo()\n      stdout <- Stdout()\n\n      //Declared: this IS a List of Animal, so a Cat can be added to it.\n      animals as List of Animal: [ Dog() ]\n      animals += Cat()\n      for animal in animals\n        stdout.println(animal.describe())\n\n      //Inferred from both elements: also a List of Animal.\n      pets <- [ Dog(), Cat() ]\n      for pet in pets\n        stdout.println(pet.describe())","migrationContext":"Java infers 'List<Animal> a = List.of(new Dog())' from the target type but rejects assigning a List<Dog> to a List<Animal> (generics are invariant; Java arrays are covariant and fail at run time with ArrayStoreException). Kotlin and Swift also take a literal's element type from the declaration. EK9 does the same: a literal takes the declared type, an existing list does not change type.","keywords":["E50030","collection","common super type","declared type","dict literal","generics","inferred type","invariant","list literal","variance"],"primaryTopics":["list literal type","declared literal type","generic invariance"],"typicalErrors":[{"error":"E50030","correct":"      animals as List of Animal: [ Dog() ]","incorrect":"      animals as List of Animal: [ Dog(), Rock() ]","explanation":"Every element of a declared List of Animal must be an Animal; Rock is not, so the error is reported at Rock(). See ek9 -h E50030."},{"error":"E50030","correct":"      pets <- [ Dog(), Cat() ]","incorrect":"      dogs <- [ Dog(), Dog() ]\n      pets as List of Animal: dogs","explanation":"'dogs' is an existing List of Dog; naming it as a List of Animal would let a Cat be added to a list that promises only Dogs. Generic types are invariant - only a literal takes the declared type. See ek9 -h E50030."}],"companions":[]}
{"id":1396,"category":"Classes and OOP","question":"Why can't my constructor call a method before every property is set?","url":"https://ek9.io/qa/QA1396.html","alternatePhrasings":["What does E08061 'the object is used before it is fully constructed' mean?","Can a property initialiser call a method of its own class?","Why can't a constructor in an open class call a method a subclass can override?","When can a constructor call its own helper methods in EK9?"],"answer":"An object cannot be used until it is fully constructed. Constructing an object runs the super constructor, then the type's property initialisers in declaration order, then the constructor body. Until every property has a value, calling a method on the object, or using 'this' as a value, can reach a property that is still unset. In Java that compiles and fails with a NullPointerException; EK9 rejects it with E08061.\n\nWHEN THE OBJECT IS COMPLETE\nIn a constructor the object is complete once every property is set on the current path - counting what a this(...) call set, which is only what the constructor it delegates to sets (a 'default private' one sets nothing; see Q1404). After that it may call its own methods:\n  Gauge()\n    -> start as Integer\n    reading: start\n    doubled: twice()\nGive a property a value at declaration ('doubled as Integer: 0') and it is already set when the constructor body starts.\n\nPROPERTY INITIALISERS\nA property initialiser runs before the constructor body, so the object is never complete there: it cannot call a method of the object - not even an inherited one, because an inherited method can call back into a method this type overrides. Compute the value with a function ('width as Integer: defaultWidth()') or take it from a constructor argument. Nor may an initialiser use 'this' at all - not as a value, not in a capture list, not even 'this.width' (write 'width'): see Q2180.\n\nOPEN AND ABSTRACT TYPES\nA subtype's own properties are only set after the super constructor returns. So the constructor of an open or abstract type can never call a method a subtype may override, even once its own properties are set. Make the helper private (a private method cannot be overridden) or compute the value with a function.\n\nNOTHING EXISTS YET\nThe arguments of super(...) and this(...) are evaluated before any construction has happened, so they cannot call methods of the object either.\n\nAN OBJECT THAT REFERS TO ITSELF\nStoring the object in one of its own properties ('next: this') is not a use of it - nothing is read through it - so it is allowed before the object is complete and sets that property. Reading through that property is a use, and waits for every property to be set. See Q1471. A child that points back to its parent - a dynamic class capturing 'this', stored in the parent's own property - is the same kind of store: see Q2181 (one child), Q2182 (two children) and Q2183 (the mistakes).\n\nSee Q104 for uninitialised properties. See Q1397 for the order property initialisers run in.","ek9Example":"#!ek9\ndefines module qa.oop.construction.complete\n\n  defines function\n\n    defaultWidth()\n      <- rtn as Integer: 4\n\n  defines class\n\n    Gauge\n      reading as Integer?\n      doubled as Integer: 0\n\n      default private Gauge()\n\n      Gauge()\n        -> start as Integer\n        reading: start\n        doubled: twice()\n\n      twice()\n        <- rtn as Integer: reading * 2\n\n      value()\n        <- rtn as Integer: doubled\n\n      default operator ?\n\n    Panel as open\n      width as Integer: defaultWidth()\n      label as String?\n\n      default private Panel()\n\n      Panel()\n        -> title as String\n        label: title\n        label: decorated()\n\n      private decorated()\n        <- rtn as String: `${label} (${width})`\n\n      describe()\n        <- rtn as String: label\n\n      default operator ?\n\n  defines program\n\n    ConstructionDemo()\n      stdout <- Stdout()\n      stdout.println($Gauge(21).value())\n      stdout.println(Panel(\"Main\").describe())","migrationContext":"Java: calling an overridable method from a constructor compiles, and a subclass override that reads its own fields sees null or 0 (Effective Java warns against it; nothing enforces it). Kotlin: 'Leaking this in constructor' and 'accessing non-final property in constructor' are warnings only. C#: the same hazard as Java, a code-analysis warning. Swift: two-phase initialisation - a class cannot call methods on self until every stored property is initialised; EK9 applies the same rule and also forbids overridable calls from the constructor of an open type. EK9: E08061 at compile time.","keywords":["E08061","NullPointerException","construction","constructor","field","helper","initialiser","initializer","method","open","overridable","override","private","property","subclass","this","two-phase"],"primaryTopics":["classes","constructors","E08061"],"typicalErrors":[{"error":"E08061","correct":"reading: start\n        doubled: twice()","incorrect":"doubled: twice()\n        reading: start","explanation":"twice() reads 'reading', which is not set yet when it is called. Set every property first, then call the object's methods."},{"error":"E08061","correct":"private decorated()","incorrect":"decorated()","explanation":"Panel is open, so a subtype could override a public decorated() - and the subtype's own properties are not set until after Panel's constructor returns. A private method cannot be overridden, so the constructor may call it."},{"error":"E08061","correct":"doubled as Integer: 0","incorrect":"doubled as Integer: twice()","explanation":"A property initialiser runs before the constructor body, when 'reading' is still unset, so it can never call a method of the object. Give it a plain value, or compute it with a function."}],"companions":[]}
{"id":1397,"category":"Classes and OOP","question":"In what order do property initialisers run, and which properties can an initialiser read?","url":"https://ek9.io/qa/QA1397.html","alternatePhrasings":["Why is 'not initialised before use' reported for a property initialiser?","Can a property initialiser read another property of the same class?","Why can't a property initialiser read a property the constructor sets?","Why does E08060 appear on a property initialiser?"],"answer":"Property initialisers run in declaration order, before the constructor body. So an initialiser may read a property of its own type only if that property is declared EARLIER and has its own initialiser:\n  stock as Integer: startingStock()\n  reserved as Integer: 2\n  available as Integer: stock - reserved\n\nWHAT IT CANNOT READ\n- A property declared later: its initialiser has not run yet.\n- Itself: 'reserved as Integer: reserved + 2' reads a value that does not exist yet.\n- A property declared with '?' and set in the constructor: the constructor body has not run yet, even though it is declared earlier.\nAll three are the same mistake - a property read before it has a value - and all report E08060 (not initialised before use). Read a property by its plain name: an initialiser may not use 'this' at all, so 'this.later' (or even 'this.stock') is E08061 - see Q2180. (A LOCAL variable whose initialiser reads itself is E08010, used before definition.)\n\nWHAT IT CAN USE\n- Earlier properties that have initialisers.\n- Functions ('startingStock()') and literals.\n- Not the object's own methods: the object is not complete while initialisers run (E08061, see Q1396).\n\nA method can read every property: by the time a method runs, the object has been fully constructed. Before these checks, each of the shapes above compiled clean and failed with a NullPointerException when the object was built.\n\nSee Q104 for uninitialised properties. See Q1396 for using the object during construction.","ek9Example":"#!ek9\ndefines module qa.oop.initialiser.order\n\n  defines function\n\n    startingStock()\n      <- rtn as Integer: 12\n\n  defines class\n\n    Inventory\n      location as String?\n      stock as Integer: startingStock()\n      reserved as Integer: 2\n      available as Integer: stock - reserved\n\n      default private Inventory()\n\n      Inventory()\n        -> place as String\n        location: place\n\n      summary()\n        <- rtn as String: `${available} available at ${location}`\n\n      default operator ?\n\n  defines program\n\n    InitialiserOrderDemo()\n      stdout <- Stdout()\n      stdout.println(Inventory(\"Leeds\").summary())","migrationContext":"Java: a field initialiser that reads a later field by simple name is an 'illegal forward reference' compile error, but 'this.later' compiles and reads the default null/0; reading a field the constructor sets also sees null/0. Kotlin: property initialisers run in order and a forward read sees null for reference types (a warning at best). C#: field initialisers cannot reference instance members at all. EK9: order is checked at compile time - a later property, the property itself, or one the constructor sets is E08060, and 'this' in an initialiser is E08061, so the only spelling is the plain name.","keywords":["E08010","E08060","NullPointerException","constructor","declaration","field","forward","initialiser","initializer","order","property","reference","self"],"primaryTopics":["classes","properties","E08060"],"typicalErrors":[{"error":"E08060","correct":"reserved as Integer: 2\n      available as Integer: stock - reserved","incorrect":"available as Integer: stock - reserved\n      reserved as Integer: 2","explanation":"Initialisers run in declaration order, so 'reserved' has no value yet when 'available' is computed. Declare a property before any initialiser that reads it."},{"error":"E08060","correct":"available as Integer: stock - reserved","incorrect":"available as Integer: length location","explanation":"'location' is declared earlier but without a value - the constructor sets it, and the constructor body runs after every initialiser. Read it in a method, or compute this value in the constructor."},{"error":"E08060","correct":"reserved as Integer: 2","incorrect":"reserved as Integer: reserved + 2","explanation":"An initialiser cannot read the property it is initialising: it has no value yet. Use a literal, an earlier property or a function."}],"companions":[]}
{"id":1398,"category":"Operators and Expressions","question":"How are defaulted comparison operators derived - does a defaulted == agree with my <=>?","url":"https://ek9.io/qa/QA1398.html","alternatePhrasings":["If I write <=> myself, what does 'default operator' generate for == and <>?","Why is == true when two objects differ in a field <=> ignores?","Why can't I use < on a record that has a Dict field?","Why is 'default operator <>' rejected when I only want inequality?"],"answer":"Every defaulted comparison is answered by the type's <=> when it has one. A defaulted == is '(a <=> b) == 0', <> is '(a <=> b) <> 0', and <, <=, >, >= compare the <=> result with zero - so a defaulted operator can never disagree with the type's ordering.\n\nWRITE <=>, DEFAULT THE REST\n  Ticket\n    id as Integer: 0\n    holder as String: \"\"\n    operator <=> as pure\n      -> other as Ticket\n      <- rtn as Integer: id <=> other.id\n    operator #? as pure\n      <- rtn as Integer: #?id\n    default operator    //generates ==, <>, <, <=, >, >= and operator ? as well\nA hand-written <=> needs a hand-written #? hashing what it compares (E07184).\nTickets are ordered by id alone, so two tickets with the same id are ==, even though their holders differ.\n\nNO ORDERING, NO <=>\nSome types have == but no <=> - Dict, List, Optional, Result. A type with such a field cannot be ordered: a bare 'default operator' gives it == (field by field) and <> (its negation), but no <, <=, >, >= or <=> - using one is E07620.\n\nEXPLICIT DEFAULTS\nWhen requesting operators one at a time, request the sameness only: 'default operator <=>' (or 'default operator ==' for a type that cannot be ordered). ==, <>, <, <=, >, >= are always generated from it and are never written or requested - 'default operator <>' is E07181.\n\nA bare 'default operator' generates every operator the type does not write itself, even beside an operator of the same name that takes a different argument (an ==(Integer) does not stop ==(Ticket) being generated).","ek9Example":"#!ek9\ndefines module qa.operators.defaulted.comparisons\n\n  defines class\n\n    Ticket\n      id as Integer: 0\n      holder as String: \"\"\n\n      Ticket()\n        ->\n          identity as Integer\n          name as String\n        id: identity\n        holder: name\n\n      operator <=> as pure\n        -> other as Ticket\n        <- rtn as Integer: id <=> other.id\n\n      //A hand-written <=> needs a hand-written #? hashing what it compares\n      operator #? as pure\n        <- rtn as Integer: #?id\n\n      default operator\n\n    Badge\n      code as String: \"\"\n\n      Badge()\n        -> text as String\n        code: text\n\n      default operator ?\n      default operator <=>\n\n  defines record\n\n    Settings\n      values as Dict of (String, Integer): Dict() of (String, Integer)\n      default operator\n\n  defines program\n\n    DefaultedComparisons()\n      stdout <- Stdout()\n      first <- Ticket(7, \"Steve\")\n      second <- Ticket(7, \"Alex\")\n      stdout.println($(first == second))\n      stdout.println($(first < Ticket(8, \"Sam\")))\n      stdout.println($(Badge(\"a\") <> Badge(\"b\")))\n      stdout.println($(Settings() == Settings()))","migrationContext":"Java: equals() and compareTo() are written independently and 'consistent with equals' is only a documented recommendation - TreeSet and HashSet can disagree about the same objects. Kotlin: data classes generate equals() but not compareTo(), so the two can drift. Rust: #[derive(PartialEq, PartialOrd)] derives both field by field, but a hand-written Ord beside a derived PartialEq can disagree. EK9: the defaulted comparisons are generated from <=>, so they agree with it by construction.","keywords":["<=>","<>","==","Dict","E07181","E07620","compareTo","comparison","consistent","default","derived","equality","equals","operator","orderable","ordering"],"primaryTopics":["operators","default operator","E07181","E07620"],"typicalErrors":[{"error":"E07620","correct":"stdout.println($(Settings() == Settings()))","incorrect":"stdout.println($(Settings() < Settings()))","explanation":"Settings has a Dict field, and a Dict has no <=>, so Settings cannot be ordered: its bare 'default operator' generates == and <> but no ordering operators."},{"error":"E07181","correct":"default operator <=>","incorrect":"default operator <>","explanation":"<> is always generated from the type's sameness (<=> or ==), so it is never written or requested - 'default operator <>' is E07181. Request (or write) the sameness, <=>, and <> comes with it."}],"companions":[]}
{"id":1399,"category":"Security and Sanitization","question":"Is a sanitized parameter still cleaned when my function is used in a stream, passed as a callback, or reached through an operator?","url":"https://ek9.io/qa/QA1399.html","alternatePhrasings":["Does sanitized work when a function is passed to map or whenPresent?","Who cleans a sanitized parameter - the caller or the function?","Can a sanitized parameter be bypassed by calling the function indirectly?","Does a sanitized operator parameter get cleaned?"],"answer":"Yes. The function or method that DECLARES a 'sanitized' parameter cleans it itself, the moment it is entered - before any of its code runs. Every route into it therefore gets the cleaned value: a direct call, a stream stage ('map with'), a callback from a built-in type ('Optional.whenPresent'), an operator, a switch case, a dispatcher, even a program's command-line argument.\n\nWHAT THE FUNCTION RECEIVES\n  safe input   -> its own copy of the String (the caller's String is never shared)\n  a threat     -> an unset String, and one log line per detector on stderr\n\nThe caller cannot bypass it and does not write anything: 'describe(sanitized text)' is an error (E07942) - the declaration decides.\n\nWHY THE RECEIVER\nA callback from built-in code has no EK9 call site at all, so cleaning at call sites could never cover every route. Cleaning in the receiver covers them by construction.\n\nOVERRIDES AND EXTERNS\nAn override may add 'sanitized' but never drop it (Q663). An extern declaration cannot promise it, because the compiler never generates its body (Q1400).\n\nSee Q661 for sanitized basics. See Q662 for safe copy patterns. See Q663 for overrides.","ek9Example":"#!ek9\ndefines module qa.sanitizeddeep.everyroute\n\n  defines function\n\n    <?-\n      Declares its parameter sanitized, so it cleans 'text' itself on entry.\n      Every route that reaches it - call, stream stage, callback - gets the cleaned value.\n    -?>\n    describe()\n      -> text as sanitized String\n      <- rtn as String: \"rejected\"\n\n      if text?\n        rtn: `accepted: ${text}`\n\n    <?-\n      A callback: built-in Optional code calls it, there is no EK9 call site.\n    -?>\n    showIt()\n      -> text as sanitized String\n      stdout <- Stdout()\n      stdout.println(`callback set: ${text?}`)\n\n    <?-\n      Builds an injection attempt at run time from two harmless pieces.\n    -?>\n    composedAttack()\n      <- rtn as String: String()\n      opening <- \"'; DROP\"\n      closing <- \" TABLE users--\"\n      rtn: opening + closing\n\n  defines class\n\n    <?-\n      An operator is a receiver too: its sanitized parameter is cleaned on entry.\n    -?>\n    Guard\n      default Guard()\n\n      operator + as pure\n        -> text as sanitized String\n        <- rtn as String: \"rejected\"\n\n        if text?\n          rtn: `accepted: ${text}`\n\n      default operator ?\n\n  defines program\n\n    EveryRouteDemo()\n      stdout <- Stdout()\n      visitorNote <- \"Hello from a visitor\"\n      attack <- composedAttack()\n\n      stdout.println(`direct: ${describe(visitorNote)}`)\n      stdout.println(`direct: ${describe(attack)}`)\n\n      results <- cat [attack] | map with describe | collect as List of String\n      for result in results\n        stdout.println(`stream: ${result}`)\n\n      Optional(attack).whenPresent(showIt)\n\n      guard <- Guard()\n      stdout.println(`operator: ${guard + attack}`)","migrationContext":"Java/Python/Go: validation is a call the developer must remember at every entry point, and a function passed as a lambda or callback is easily called without it. EK9: the function that declares 'sanitized' cleans the parameter on entry, so no route - lambda, stream, callback or operator - can skip it.","keywords":["E07942","bypass","callback","entry","injection","map","operator","receiver","route","sanitized","security","stream","whenPresent"],"primaryTopics":["sanitized routes","sanitized receiver"],"typicalErrors":[{"error":"E07942","correct":"describe(visitorNote)","incorrect":"describe(sanitized visitorNote)","explanation":"The caller never writes 'sanitized' - the function declares it on its parameter and cleans the value itself on entry. See ek9 -h E07942 for details."}],"companions":[]}
{"id":1400,"category":"Security and Sanitization","question":"Can an extern function or method declare a sanitized parameter?","url":"https://ek9.io/qa/QA1400.html","alternatePhrasings":["What is E07944 sanitized on an extern parameter?","Why is sanitized rejected in a defines extern module?","How do I sanitize input passed to an externally implemented function?","Who cleans the input of a Java-backed extern method?"],"answer":"No (E07944). A function or method cleans its own 'sanitized' parameters at the start of the body the compiler generates for it. An extern declaration ('defines extern module') only describes a body implemented somewhere else - for example in Java - so the compiler has nowhere to put the cleaning, and 'sanitized' there would promise cleaning that never happens.\n\nTHE TWO CORRECT PATTERNS\n1. Clean inside the external implementation. In Java, call InputSanitizer on the incoming value - this is what EK9's own built-in types do.\n2. Wrap the extern call in an ordinary EK9 function whose parameter is 'sanitized'. That wrapper's body IS generated, so the value is cleaned before it is passed on:\n  cleanThenStore()\n    -> input as sanitized String\n    <- rtn as Boolean: storeRecord(input)\n\nThe extern signature itself stays plain:\n  //inside a 'defines extern module ...' declaration\n  storeRecord()\n    -> recordText as String\n    <- rtn as Boolean?\n\nSee Q1399 for how the receiver cleans a sanitized parameter on every route. See Q661 for where sanitized is allowed.","ek9Example":"#!ek9\ndefines extern module qa.sanitizeddeep.externstore\n\n  defines function\n\n    <?-\n      Implemented outside EK9 (for example in Java). The compiler never generates this body,\n      so it cannot clean 'recordText' - the implementation must call InputSanitizer itself.\n    -?>\n    storeRecord()\n      -> recordText as String\n      <- rtn as Boolean?\n\n  defines class\n\n    <?-\n      An externally implemented class: its methods follow the same rule.\n    -?>\n    RecordStore\n      RecordStore()\n\n      save()\n        -> entryText as String\n        <- rtn as Boolean?","migrationContext":"Java: a native or JNI method can claim anything in its documentation; nothing checks it. Kotlin/Swift: expect/extern declarations carry no security contract. EK9: an extern signature cannot claim 'sanitized' - only a body the compiler generates can keep that promise, so the claim is rejected at compile time (E07944).","keywords":["E07944","InputSanitizer","Java","extern","external","injection","native","sanitized","security","wrapper"],"primaryTopics":["E07944","sanitized extern"],"typicalErrors":[{"error":"E07944","correct":"-> recordText as String","incorrect":"-> recordText as sanitized String","explanation":"The compiler never generates an extern body, so it cannot clean the parameter - keep the extern signature plain and clean the input in the implementation, or in an EK9 wrapper with a sanitized parameter. See ek9 -h E07944 for details."}],"companions":[]}
{"id":1401,"category":"Purity Contracts","question":"What happens when a method comes from both a pure parent and a non-pure parent?","url":"https://ek9.io/qa/QA1401.html","alternatePhrasings":["What is E05290 parents disagree on pure?","Can a class extend a non-pure super and implement a pure trait with the same method?","Is 'pure' checked against every trait, or only the first parent?","Why does my override conflict with two parents about purity?"],"answer":"'pure' is always part of a method's contract, so an override is checked against EVERY parent that declares the method - the super class and each trait. When all of them are pure the override must be pure (E05150); when none are, it must not be (E05160). When they DISAGREE - a non-pure super and a pure trait, or two traits one way and the other - no single method can honour both contracts, and the class is rejected with E05290.\n\nWHY\nA caller holding the pure trait relies on the method having no side effects; a caller holding the non-pure super relies on it being free to have them. One body cannot be both.\n\nFIX\nMake the parents agree - mark both declarations 'pure', or neither - or stop combining them on one type.\n\nThis differs from 'sanitized', which an override may ADD but never drop: 'pure' must match every parent exactly.\n\nSee Q561 for pure overrides. See Q569 for traits and pure contracts. See Q663 for how 'sanitized' differs.","ek9Example":"#!ek9\ndefines module qa.purity.multipleparents\n\n  defines trait\n\n    <?-\n      A pure contract: label() has no side effects.\n    -?>\n    Labelled\n      label() as pure abstract\n        <- rtn as String?\n\n  defines class\n\n    <?-\n      The super declares label() pure too, so the parents agree.\n    -?>\n    Shape as open\n      label() as pure\n        <- rtn as String: \"shape\"\n\n      default operator ?\n\n    <?-\n      Circle stands in for BOTH Shape.label() and Labelled.label() - it must be pure.\n    -?>\n    Circle extends Shape with trait of Labelled\n      override label() as pure\n        <- rtn as String: \"circle\"\n\n      default operator ?\n\n  defines program\n\n    MultipleParentsDemo()\n      stdout <- Stdout()\n      asShape as Shape: Circle()\n      asLabelled as Labelled: Circle()\n      stdout.println(asShape.label())\n      stdout.println(asLabelled.label())","migrationContext":"Java: no purity in the type system - interfaces cannot promise side-effect freedom. Kotlin/Scala: no enforced purity. Haskell: purity is global (IO in the type), so the conflict cannot arise. EK9: 'pure' is part of every declaration's contract and the compiler rejects a type whose parents disagree on it.","keywords":["E05150","E05290","conflict","contract","multiple","override","parents","pure","purity","super","trait"],"primaryTopics":["E05290","pure multiple parents"],"typicalErrors":[{"error":"E05290","correct":"label() as pure\n        <- rtn as String: \"shape\"","incorrect":"label()\n        <- rtn as String: \"shape\"","explanation":"Shape.label() would no longer be pure while the trait Labelled.label() is, so Circle's override would have to honour two contracts that disagree. See ek9 -h E05290 for details."}],"companions":[]}
{"id":1402,"category":"Operators and Expressions","question":"How do I define equality and ordering for my own type, and why can't I write '==' beside '<=>'?","url":"https://ek9.io/qa/QA1402.html","alternatePhrasings":["Why is E07181 reported on my hand-written '<' or '=='?","Why must '#?' be written when I write '<=>' (E07184)?","Why can't a subclass override '<=>' or '==' (E07182)?","How do I sort my type by a different field?","What is 'sameness' in EK9?"],"answer":"A type defines its SAMENESS once, and every other comparison is generated from it - two ways to say 'equal' eventually disagree.\n\nCHOOSE ONE\n- An ORDERED type writes '<=>'. The compiler generates '==' '<>' '<' '<=' '>' '>=' from it: '==' is (a <=> b) == 0.\n- An EQUATABLE-ONLY type (a 2D point: equality means something, ordering does not) writes '=='. '<>' is generated and there is no '<'.\nEither can be defaulted instead ('default operator', 'default operator <=>', 'default operator ==').\n\nTHE RULES\n- Writing, or asking for, any other comparison with the type itself is E07181 - including one taking a super, a trait or Any ('==(Any)').\n- '#?' goes with sameness: a hand-written '<=>' or '==' needs a hand-written '#?' hashing what it compares (E07184); defaulted sameness gets a generated '#?'.\n- Sameness is declared once per hierarchy: a subtype inherits it and cannot override it (E07182). Any subtype refinement of an ordering is non-transitive once base and subtype values are mixed, so a sort would depend on input order.\n- It is concrete where declared (E07183): a trait that orders writes the body over its own abstract accessors, 'rank() <=> other.rank()'.\n- A GENERATED '==' or '<=>' between two DIFFERENT concrete classes of one hierarchy (a Leaf and a Middle) is unset; write the root's operator by hand when objects of different classes must compare.\n\nBECAUSE '==' COMES FROM '<=>'\n'<=>' defines what makes two values the SAME. Here two Person values with the same id are ==. If '<=>' compared age, two different people of the same age would be == and a Set would keep only one of them.\n\nANOTHER ORDERING\nSort by anything else with a comparator function: 'sort by byAge'. It never needs a second '<=>'.\n\nThis is Java's equals/hashCode/compareTo contract (Effective Java items 10, 11 and 14) made a compile error, with the derived operators generated so they cannot drift.","ek9Example":"#!ek9\ndefines module qa.operators.sameness.once\n\n  defines function\n\n    <?-\n      Another ordering is a comparator function given to sort - never a second '<=>'.\n    -?>\n    byAge() as pure\n      ->\n        first as Person\n        second as Person\n      <- rtn as Integer: first.age() <=> second.age()\n\n  defines class\n\n    <?-\n      An ORDERED type: '<=>' defines its sameness. '==' '<>' '<' '<=' '>' '>=' are generated from it,\n      and '#?' hashes exactly what '<=>' compares.\n    -?>\n    Person as open\n      id as Integer?\n      name as String?\n      years as Integer?\n\n      Person()\n        id: Integer()\n        name: String()\n        years: Integer()\n\n      Person()\n        ->\n          identifier as Integer\n          fullName as String\n          age as Integer\n        id: identifier\n        name: fullName\n        years: age\n\n      identity() as pure\n        <- rtn as Integer: id\n\n      age() as pure\n        <- rtn as Integer: years\n\n      operator <=> as pure\n        -> other as Person\n        <- rtn as Integer: id <=> other.identity()\n\n      operator #? as pure\n        <- rtn as Integer: #?id\n\n      operator $ as pure\n        <- rtn as String: name\n\n      override operator ? as pure\n        <- rtn as Boolean: id?\n\n    <?-\n      A SUBTYPE inherits its super's sameness and adds none of its own.\n    -?>\n    Student extends Person\n      subject as String: \"maths\"\n\n      override operator ? as pure\n        <- rtn as Boolean: identity()?\n      default operator $\n\n    <?-\n      An EQUATABLE-ONLY type: '==' defines its sameness, '<>' is generated, and there is no ordering.\n    -?>\n    Point\n      across as Integer?\n      down as Integer?\n\n      Point()\n        across: Integer()\n        down: Integer()\n\n      Point()\n        ->\n          x as Integer\n          y as Integer\n        across: x\n        down: y\n\n      acrossOf() as pure\n        <- rtn as Integer: across\n\n      downOf() as pure\n        <- rtn as Integer: down\n\n      operator == as pure\n        -> other as Point\n        <- rtn as Boolean: across == other.acrossOf() and down == other.downOf()\n\n      operator #? as pure\n        <- rtn as Integer: #?across + #?down\n\n      override operator ? as pure\n        <- rtn as Boolean: across? and down?\n\n  defines program\n\n    SamenessOnce()\n      stdout <- Stdout()\n\n      ada <- Person(1, \"Ada\", 36)\n      bob <- Person(2, \"Bob\", 29)\n      stdout.println(`ada == ada again: ${ada == Person(1, \"Ada\", 36)}`)\n      stdout.println(`ada < bob: ${ada < bob}`)\n\n      byIdentity <- cat [bob, ada] | sort | collect as List of Person\n      stdout.println(`by identity: ${byIdentity}`)\n\n      youngestFirst <- cat [ada, bob] | sort by byAge | collect as List of Person\n      stdout.println(`by age: ${youngestFirst}`)\n\n      here <- Point(1, 2)\n      stdout.println(`points differ: ${here <> Point(2, 1)}`)","migrationContext":"Java: equals, hashCode and compareTo are written separately and only conventions and linters (Sonar S1206, S1210, S2162) ask that they agree; subclasses that add a field break the equals contract (Effective Java item 10). Kotlin: data classes generate equals/hashCode but not compareTo, and cannot be extended. C#: IEquatable, GetHashCode and IComparable are separate too. EK9: one '<=>' (or '==') is the single source; the rest are generated, '#?' must agree, and sameness is sealed where it is declared.","keywords":["#?","<=>","<>","==","E07181","E07182","E07183","E07184","comparator","default operator","equality","hashcode","ordering","sameness","sort"],"primaryTopics":["operators","E07181","E07182","E07184"],"typicalErrors":[{"error":"E07181","correct":"operator <=> as pure\n        -> other as Person\n        <- rtn as Integer: id <=> other.identity()","incorrect":"operator < as pure\n        -> other as Person\n        <- rtn as Boolean: years < other.age()","explanation":"'<' is generated from '<=>', so writing it gives the type a second, disagreeing notion of order. For another ordering, write a comparator function and use 'sort by'."},{"error":"E07184","correct":"operator #? as pure\n        <- rtn as Integer: #?id","incorrect":"default operator #?","explanation":"A hand-written '<=>' decides what is equal, so the hash must be hand-written over the same fields: a defaulted '#?' hashes every field and would give equal values different hashes."},{"error":"E07182","correct":"      subject as String: \"maths\"","incorrect":"      subject as String: \"maths\"\n\n      override operator <=> as pure\n        -> other as Student\n        <- rtn as Integer: identity() <=> other.identity()","explanation":"A subtype inherits its super's sameness and cannot change it. An ordering by another field belongs in a comparator function."}],"companions":[]}
{"id":1403,"category":"Classes and OOP","question":"Why does a type that constructs itself in a property initialiser or constructor fail to compile?","url":"https://ek9.io/qa/QA1403.html","alternatePhrasings":["What is E08263 construction recursion?","Why is 'next as Node: Node()' an error?","How do I write a linked node or a tree in EK9 without infinite recursion?","Why does Node extends Base of Node fail when Base constructs T?"],"answer":"If constructing a type ALWAYS constructs the same type again, construction can never complete - at run time it is a stack overflow - so the compiler rejects it (E08263) and shows the cycle. It follows only what always runs when an object is built: the super constructor, the property initialisers (they run for EVERY instance, before the constructor body), a this(...) delegation and its arguments, and the calls in a constructor body, function or closed method that are not inside a condition, loop, catch, short-circuit or ':=?'. The cycle can be direct ('next as Node: Node()'), between two types whose initialisers build each other, through a function, a this(...) delegation cycle, or a generic: 'Node extends Base of Node' where Base does 'me as T: T()' builds a Node while building a Node.\n\nFIX\nHold a link in an 'Optional of Node', which starts empty, and fill it when there is something to link - and give the type its own '?' if an empty link should not make it unset (a defaulted '?' needs every property set). Hold children in a 'List of Node', which also starts empty. Build the recursive part only under a condition that ends it - recursion under a condition is legal, because the compiler does not need to prove it ends. A plain 'next as Node?' does not help on its own: every property must be initialised before use (E08060).\n\nSee Q1396 for using an object before it is constructed. See Q1397 for the order property initialisers run in.","ek9Example":"#!ek9\ndefines module qa.classes.constructionrecursion\n\n  defines class\n\n    <?-\n      A linked node: the link starts empty, so building a Node never builds another.\n      A Node is set once it has a label - an empty link must not make it unset.\n    -?>\n    Node\n      label as String: String()\n      next as Optional of Node: Optional() of Node\n\n      Node()\n        -> text as String\n        label: text\n\n      link()\n        -> node as Node\n        next: Optional(node)\n\n      describe()\n        <- rtn as String: label\n        if next?\n          rtn: `${label} -> ${next.get().describe()}`\n\n      override operator ? as pure\n        <- rtn as Boolean: label?\n\n    <?-\n      A tree built to a depth: a child is built only while depth is above zero.\n    -?>\n    Tree\n      children as List of Tree: List() of Tree\n\n      Tree()\n        -> depth as Integer\n        if depth > 0\n          children += Tree(depth - 1)\n\n      size()\n        <- rtn as Integer: 1\n        for child in children\n          rtn += child.size()\n\n      default operator ?\n\n  defines program\n\n    ConstructionDemo()\n      stdout <- Stdout()\n      first <- Node(\"first\")\n      first.link(Node(\"second\"))\n      stdout.println(first.describe())\n      treeSize <- Tree(3).size()\n      stdout.println($treeSize)","migrationContext":"Java, C#, Kotlin, Swift, Rust and Go all compile 'class Node { Node next = new Node(); }' (or its equivalent) and overflow the stack at run time; Java, C# and Kotlin reject only a this(...) delegation cycle. EK9 rejects every construction that always constructs its own type again, including through functions, closed methods and generics, as a compile error.","keywords":["E08263","construction","constructor","initialiser","linked","node","optional","overflow","property","recursion","stack","tree"],"primaryTopics":["E08263","construction recursion"],"typicalErrors":[{"error":"E08263","correct":"if depth > 0\n          children += Tree(depth - 1)","incorrect":"children += Tree(depth - 1)","explanation":"Without the condition every Tree builds another Tree, so construction can never complete. See ek9 -h E08263 for details."}],"companions":[]}
{"id":1404,"category":"Constructor Delegation","question":"Why can't my class construct an object of itself through its private default constructor?","url":"https://ek9.io/qa/QA1404.html","alternatePhrasings":["What is E08264 CONSTRUCTOR_LEAVES_PROPERTIES_UNSET?","Why is calling my 'default private' constructor from a method an error?","Does this() to an empty private constructor initialise my properties?","Why does a constructor that delegates with this() still have to set every property?"],"answer":"A private constructor may leave properties without a value - the 'default private Label()' idiom - only because it is meant to be reached through this() from a constructor that then sets them. Any other way of reaching it, such as the type constructing an object of itself with 'Label()' in a method, would build a Label whose 'text' has no value, so the first read of it (or the generated '?') would fail at run time. EK9 rejects that call with E08264.\n\nJUDGED THROUGH THE this() CHAIN\nEach constructor is judged on what it leaves set: its own body plus, through this(...), whatever the constructor it delegates to sets. A 'default private X()' that sets nothing gives nothing, so a public constructor that calls this() must set every property itself (otherwise E08060 is reported at the property). After this(...) only what the delegate set has a value: reading any other property, or calling a method of the object, before setting it is E08060 or E08061.\n\nFIX\nConstruct through a constructor that sets every property: pass the values ('Label(\"\")'), or write a private constructor that sets them all. Such a constructor may be used anywhere in the type ('restarted()' below), and after this(...) to it the object is complete.\n\nSee Q886 for why the default constructor must be private. See Q580 for this() delegation. See Q1396 for using an object before it is constructed.","ek9Example":"#!ek9\ndefines module qa.constructordelegation.incompleteprivate\n\n  defines class\n\n    <?-\n      The private default sets nothing. It may only be reached through this() from a constructor\n      that then sets 'text' - never to construct a Label directly.\n    -?>\n    Label\n      text as String?\n\n      default private Label()\n\n      Label()\n        -> initial as String\n        this()\n        text: initial\n\n      blank()\n        <- rtn as Label: Label(\"\")\n\n      contents()\n        <- rtn as String: text\n\n      default operator ?\n\n    <?-\n      A private constructor that sets every property may be used anywhere in the type,\n      and after this(...) to it what it set can be used straight away.\n    -?>\n    Tally\n      total as Integer?\n\n      default private Tally()\n\n      private Tally()\n        -> start as Integer\n        total: start\n\n      Tally()\n        ->\n          start as Integer\n          extra as Integer\n        this(start)\n        total += extra\n\n      restarted()\n        <- rtn as Tally: Tally(0)\n\n      current()\n        <- rtn as Integer: total\n\n      default operator ?\n\n  defines program\n\n    IncompletePrivateConstructorDemo()\n      stdout <- Stdout()\n\n      named <- Label(\"named\")\n      cleared <- named.blank()\n      stdout.println(`${named.contents()} [${cleared.contents()}] ${cleared?}`)\n\n      tallied <- Tally(2, 3)\n      stdout.println(`${tallied.current()} ${tallied.restarted().current()}`)","migrationContext":"Java requires a final field to be assigned by every constructor, directly or through this(...), but a non-final field may be left null by any constructor - including a private one that a factory method calls. Kotlin and Swift require every non-optional property to be initialised by every constructor or initialiser. EK9 applies the rule to every property declared without a value, follows this(...) chains, and lets a private constructor leave properties unset only when it is reached through this().","keywords":["E08264","constructor","default","delegation","factory","private","property","uninitialised","unset"],"primaryTopics":["E08264","private constructor leaves properties unset","this() delegation initialisation"],"typicalErrors":[{"error":"E08264","correct":"Label(\"\")","incorrect":"Label()","explanation":"'Label()' is the private default, which sets nothing - it may only be reached through this() from a constructor that then sets 'text'. Construct through a constructor that sets every property. See ek9 -h E08264 for details."}],"companions":[]}
{"id":1405,"category":"Operators and Expressions","question":"What does the merge operator ':~:' do to a value that is already set?","url":"https://ek9.io/qa/QA1405.html","alternatePhrasings":["Does ':~:' overwrite an Integer or String that already has a value?","Why did merging 5 into 10 leave 10?","When does merge take the source value for a single value?","Is merge the same as copy for a Date or Money?"],"answer":"Nothing. ':~:' never overwrites a set value; it fills whatever is unset or missing, element by element, recursing into compatible nested structures; Lists append, Sets take the union. The first value recorded wins.\n\nSINGLE VALUES\nFor a single value (Integer, Float, String, Boolean, Character, Date, Time, Money, Optional and the rest) that means: the target takes the source ONLY when the target is unset and the source is set.\n  count <- Integer()\n  count :~: 5\n  count :~: 7\n  stdout.println($count)\nThe first merge fills the unset count with 5; the second is ignored because count is now set.\n\nTHE FOUR CASES\n- set target, set source: the target is kept\n- set target, unset source: the target is kept\n- unset target, set source: the target takes the source\n- unset target, unset source: the target stays unset\nMoney follows the same rule whatever the currency: a set 10.00#GBP merged with 5.00#USD stays 10.00#GBP.\n\nTO OVERWRITE\nUse ':=:' (copy) or ':^:' (replace) when you mean to replace a set value.\n\nNOT THE SAME AS A STREAM\nStreaming into a value with '>' is a fold, not a merge, so 'cat [1, 2, 3] > total' still sums to 6.\n\nSee Q1406 for merging Dicts gathered from several tasks. See Q1409 for Lists and Sets. See Q1410 for writing a merge of your own. See Q1058 for copy, replace and merge on records.","ek9Example":"#!ek9\ndefines module qa.operators.mergerulesinglevalues\n\n  defines program\n\n    MergeRuleSingleValuesDemo()\n      stdout <- Stdout()\n\n      //A set target is never overwritten\n      kept <- 10\n      kept :~: 20\n      stdout.println(`integer set kept: ${kept}`)\n\n      //An unset target takes a set source\n      filled <- Integer()\n      filled :~: 30\n      stdout.println(`integer unset filled: ${filled}`)\n\n      text <- \"original\"\n      text :~: \"replacement\"\n      stdout.println(`string set kept: ${text}`)\n\n      noText <- String()\n      noText :~: \"supplied\"\n      stdout.println(`string unset filled: ${noText}`)\n\n      //Money keeps a set value whatever the source currency\n      price <- 10.00#GBP\n      price :~: 5.00#USD\n      stdout.println(`money other currency kept: ${price}`)\n\n      //The first value recorded wins\n      first <- Integer()\n      second <- 5\n      first :~: second\n      first :~: 7\n      stdout.println(`first recorded wins: ${first}`)\n\n      //Streaming into a value is a fold, not a merge\n      total <- Integer()\n      cat [1, 2, 3] > total\n      stdout.println(`pipe still sums: ${total}`)","migrationContext":"Java: there is no merge operator; Map.putIfAbsent and Optional.or are the nearest single-purpose equivalents. Kotlin: '?:' gives a default for a null, but does not mutate. Python: dict.setdefault for one key. EK9: one operator, ':~:', with one rule on every built-in type - a set value is never overwritten, an unset one is filled.","keywords":[":~:","E08080","Money currency","fill","first recorded wins","merge","overwrite","single value","unset target"],"primaryTopics":["merge single value","merge never overwrites"],"typicalErrors":[{"error":"E08080","correct":"      first :~: second","incorrect":"      first :~: first","explanation":"Merging a variable into itself can never change it, so the compiler reports a self assignment. Merge a different value in. See ek9 -h E08080 for details."}],"companions":[]}
{"id":1406,"category":"Common Collection Tasks","question":"How do I combine partial results from several tasks into the fullest Dict?","url":"https://ek9.io/qa/QA1406.html","alternatePhrasings":["Each task found some of the fields - how do I gather them into one Dict?","Which value wins when two Dicts I merge both have the key?","How do I fill the gaps in one Dict from another without losing what it has?","Does the order I merge Dicts in matter?","Does merge copy the nested Dicts it takes, or share them with the source?"],"answer":"Merge each task's Dict into one with ':~:'. Merge never overwrites a set value; it fills whatever is unset or missing, key by key - so the first value recorded for a key wins and every gap is filled.\n\nKEY BY KEY\n- a key the target lacks is added\n- a value the target holds unset is filled from a set source value\n- a value the target holds set is kept\n\nGATHERING\n  gathered <- Dict() of (String, String)\n  gathered :~: {\"name\": \"Ada\", \"role\": String()}\n  gathered :~: {\"name\": \"Grace\", \"role\": \"engineer\"}\n  stdout.println($gathered)\nThe first task recorded the name but not the role; the second supplies the role, and its name is ignored.\n\nORDER MATTERS\nBecause the first recorded value wins, merging A then B can differ from merging B then A when both set the same key. Merge the most trusted source first.\n\nNOT THE SAME AS +\n'+' builds a new Dict and, where keys overlap, the right-hand value is used. ':~:' changes the target and keeps what it already has.\n\nTAKEN MEANS SHARED, NOT COPIED\nWhat the target takes - a key it lacked, a value it held unset - is the same object the source holds (shallow, as a List's ':=:' is). Because merge recurses, a LATER merge into a structure the target took also changes the source's inner object:\n  taskOne <- {\"person\": {\"name\": \"Ada\"}}\n  taskTwo <- {\"person\": {\"role\": \"engineer\"}}\n  combined <- Dict() of (String, Dict of (String, String))\n  combined :~: taskOne   //takes taskOne's own inner Dict\n  combined :~: taskTwo   //merges into that same Dict - taskOne now has the role too\n  stdout.println($taskOne)\n  stdout.println($combined)\nTo keep every source independent, give the target its own fresh inner structure first, so each merge fills the target's Dict rather than taking a source's:\n  keptApart <- {\"person\": Dict() of (String, String)}\n\nSee Q1405 for the rule on single values. See Q1408 for Dicts nested inside Dicts. See Q186 for merging Lists and Dicts.","ek9Example":"#!ek9\ndefines module qa.collectiontasks.mergegatherpartialresults\n\n  defines constant\n    PERSON <- \"person\"\n    NAME <- \"name\"\n    ROLE <- \"role\"\n\n  defines function\n\n    personTask()\n      ->\n        field as String\n        found as String\n      <- rtn as Dict of (String, Dict of (String, String)): {PERSON: {field: found}}\n\n    fieldOf()\n      ->\n        results as Dict of (String, Dict of (String, String))\n        field as String\n      <- rtn as String: results.getOrDefault(PERSON, Dict() of (String, String)).getOrDefault(field, \"none\")\n\n    taskResults()\n      <- rtn as List of Dict of (String, String): [\n        {\"name\": \"Ada\", \"role\": String()},\n        {\"name\": \"Grace\", \"role\": \"engineer\"},\n        {\"city\": \"London\"}\n        ]\n\n  defines program\n\n    MergeGatherPartialResultsDemo()\n      stdout <- Stdout()\n\n      //Each task found some fields; merge them all for the fullest set\n      gathered <- Dict() of (String, String)\n      for task in taskResults()\n        gathered :~: task\n\n      for entry in gathered\n        stdout.println(`${entry.key()}: ${entry.value()}`)\n\n      //The first value recorded for a key wins, so order matters\n      first <- {\"colour\": \"red\"}\n      second <- {\"colour\": \"blue\"}\n\n      firstThenSecond <- Dict() of (String, String)\n      firstThenSecond :~: first\n      firstThenSecond :~: second\n      stdout.println(`first then second: ${firstThenSecond.getOrDefault(\"colour\", \"none\")}`)\n\n      secondThenFirst <- Dict() of (String, String)\n      secondThenFirst :~: second\n      secondThenFirst :~: first\n      stdout.println(`second then first: ${secondThenFirst.getOrDefault(\"colour\", \"unknown\")}`)\n\n      //A key the target lacked is TAKEN - the source's own inner Dict, shared not copied\n      who <- \"Ada\"\n      job <- \"engineer\"\n      taskOne <- personTask(NAME, who)\n      taskTwo <- personTask(ROLE, job)\n      combined <- Dict() of (String, Dict of (String, String))\n      combined :~: taskOne\n      combined :~: taskTwo\n      stdout.println(`task one shared, its role: ${fieldOf(taskOne, ROLE)}`)\n      stdout.println(`combined: ${fieldOf(combined, NAME)} ${fieldOf(combined, ROLE)}`)\n\n      //Give the target its own fresh inner Dict first: each merge fills it, the sources stay apart\n      taskThree <- personTask(NAME, who)\n      taskFour <- personTask(ROLE, job)\n      keptApart <- {PERSON: Dict() of (String, String)}\n      keptApart :~: taskThree\n      keptApart :~: taskFour\n      stdout.println(`task three kept apart, its role: ${fieldOf(taskThree, ROLE)}`)\n      stdout.println(`kept apart gathered: ${fieldOf(keptApart, NAME)} ${fieldOf(keptApart, ROLE)}`)","migrationContext":"Java: map.putAll(other) overwrites existing keys; gathering 'first found wins' needs putIfAbsent in a loop, and a null value is not filled. Python: dict.update overwrites; {**b, **a} keeps a's values but builds a new dict. Kotlin: map + other lets other win. EK9: target :~: source keeps every set value and fills missing keys and unset values in place, recursing into nested Dicts.","keywords":[":~:","dict","fill gaps","first recorded wins","gather","merge","merge order","partial results","putIfAbsent","shallow merge","shared not copied"],"primaryTopics":["merge partial results","gather dict results"],"typicalErrors":[{"error":"E50060","correct":"      gathered :~: task","incorrect":"      gathered.putAll(task)","explanation":"EK9's Dict has no putAll() method; ':~:' is the merge, and unlike putAll() it never overwrites a value the target already holds. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1407,"category":"JSON and Data Processing","question":"How do I merge two partial CSV tables so the gaps in one are filled from the other?","url":"https://ek9.io/qa/QA1407.html","alternatePhrasings":["What does ':~:' do to a CSV?","How do I fill empty CSV cells from another CSV?","Does merging CSVs append the rows?","Which cell wins when two CSVs I merge both have a value?"],"answer":"Use ':~:'. Merge never overwrites a set value; it fills whatever is unset or missing, element by element. A CSV's elements are its header, each data row (by position) and each cell (by row and column):\n- a wholly unset CSV takes the whole source;\n- otherwise a missing header is filled, a row past the target's last row is added, and a cell that is missing or empty is filled from the same row and column of the source;\n- every cell the target holds is kept - the first recorded value wins;\n- if both CSVs have headers and they differ, nothing changes, so mismatched columns are never welded together.\nAn empty cell is CSV's only way to say \"no value\", which is why it counts as unset.\n\nMERGE IS NOT STACKING\nTo stack the rows of two CSVs, use '+=' (in place) or '+' (a new CSV). ':~:' lines the two tables up position by position instead.\n\nA ROW\nMerging a CSVRow into a CSV treats it as a one-row source, so it fills row 0.\n\nSee Q1364 for reading and writing CSV. See Q1406 for gathering partial results in Dicts. See Q1405 for the rule on single values.","ek9Example":"#!ek9\ndefines module qa.jsonanddata.mergecsvfill\n\n  defines function\n\n    table()\n      -> text as String\n      <- rtn as CSV: CSV()\n      result <- CSV().parse(text)\n      if result.isOk()\n        rtn := result.ok()\n\n  defines program\n\n    MergeCsvFillDemo()\n      stdout <- Stdout()\n\n      //Hydrogen is missing its weight, Helium its symbol, and there is no Lithium row\n      elements <- table(\"name,weight,symbol\\nHydrogen,,H\\nHelium,4.0026\")\n      found <- table(\"name,weight,symbol\\nHydrogen,1.008,X\\nHelium,9,He\\nLithium,6.94,Li\")\n\n      //Fill the gaps; Hydrogen keeps H and Helium keeps 4.0026\n      elements :~: found\n      stdout.println(\"filled:\")\n      stdout.print($elements)\n\n      //Stacking rows is += (or +), not merge\n      stacked <- table(\"name,weight,symbol\\nHydrogen,1.008,H\")\n      stacked += found\n      stdout.println(`stacked rows: ${stacked.rows()}`)","migrationContext":"Java: no built-in CSV type; filling blanks from a second file is a hand-written nested loop. Python: pandas DataFrame.combine_first does the same job (fills NaN from the other frame, keeps existing values). EK9: csv :~: other does it in place, guarded by the headers.","keywords":[":~:","combine_first","csv","empty cell","fill","merge","partial table","stack rows"],"primaryTopics":["merge csv","fill csv cells"],"typicalErrors":[],"companions":[]}
{"id":1408,"category":"Common Collection Tasks","question":"Does ':~:' merge a Dict of Dict all the way down, or replace the inner Dict?","url":"https://ek9.io/qa/QA1408.html","alternatePhrasings":["How does merge treat nested dictionaries?","What happens when I merge a Dict whose values are Dicts?","What if the same key holds a Dict on one side and a String on the other when I merge?","Is EK9 merge a deep merge?"],"answer":"It merges all the way down. ':~:' never overwrites a set value; it fills whatever is unset or missing, element by element, recursing into compatible nested structures.\n\nRECURSION\nWhen the target and the source both hold a set value under the same key, and the source's value is the same kind of structure as the target's (a Dict in a Dict, a List in a Dict, a CSV in a Dict), the source's value is merged INTO the target's by the same rule: missing keys added, unset values filled, set values kept, Lists appended. This repeats at every level.\n\nINCOMPATIBLE NESTING\nWhen the two values are not the same kind of structure - a Dict on one side and a String on the other - the target's set value is kept whole. Merge never forces a different type in.\n\nSHARED, NOT COPIED\nA value the target takes from the source (a key it lacked) is the same object as the source's, as with a Dict's ':=:' (its values are shared). Merge into it later and the source sees the change. To keep every source independent, give the target its own fresh inner Dict before merging, so each merge fills the target's Dict instead of taking a source's (Q1406 runs both).\n\nSee Q1406 for gathering partial results. See Q1405 for the rule on single values. See Q1409 for Lists and Sets.","ek9Example":"#!ek9\ndefines module qa.collectiontasks.mergenesteddicts\n\n  defines program\n\n    MergeNestedDictsDemo()\n      stdout <- Stdout()\n\n      //Three levels: user -> settings -> display\n      saved <- {\"user\": {\"display\": {\"theme\": \"dark\"}}}\n      defaults <- {\"user\": {\"display\": {\"theme\": \"light\", \"font\": \"mono\"}, \"locale\": {\"language\": \"en\"}}}\n\n      //Recurse: theme is kept, font is filled, the locale Dict is added\n      saved :~: defaults\n\n      user <- saved.getOrDefault(\"user\", Dict() of (String, Dict of (String, String)))\n      display <- user.getOrDefault(\"display\", Dict() of (String, String))\n      locale <- user.getOrDefault(\"locale\", Dict() of (String, String))\n      stdout.println(`theme: ${display.getOrDefault(\"theme\", \"?\")}`)\n      stdout.println(`font: ${display.getOrDefault(\"font\", \"-\")}`)\n      stdout.println(`language: ${locale.getOrDefault(\"language\", \"none\")}`)\n      stdout.println(`settings level keys: ${length user}`)\n\n      //A Dict on one side and a String on the other: the target keeps its value whole\n      //(values of different types share only Any, so each Dict declares its value type)\n      mixed as Dict of (String, Any): {\"settings\": {\"mode\": \"fast\"}, \"label\": \"plain\"}\n      incoming as Dict of (String, Any): {\"settings\": \"flat text\", \"label\": {\"nested\": \"dict\"}}\n      mixed :~: incoming\n      stdout.println(`incompatible kept: ${mixed}`)","migrationContext":"Java: Map.putAll replaces an inner map whole; a deep merge needs a recursive helper or a library. JavaScript: Object.assign and spread are shallow; lodash merge is deep. Python: dict.update is shallow. EK9: ':~:' is the deep merge, and it keeps every set value instead of overwriting it.","keywords":[":~:","deep merge","dict of dict","incompatible nesting","merge","nested dict","recursive merge"],"primaryTopics":["merge nested dict","deep merge dict"],"typicalErrors":[],"companions":[]}
{"id":1409,"category":"Collections and Data Structures","question":"What is the difference between merging a List and merging a Set with ':~:'?","url":"https://ek9.io/qa/QA1409.html","alternatePhrasings":["Does ':~:' on a List remove duplicates?","Does merging two Sets keep both copies of a shared item?","In what order does a List merge add items?","Why does merging Lists append when merging Dicts does not overwrite?"],"answer":"A List APPENDS and a Set takes the UNION. ':~:' never overwrites a set value and fills whatever is unset or missing - but a List or a Set has no element to fill (no key, no position that can be unset), so merging one simply combines.\n\nLIST\nThe source's items are added at the end, in order, duplicates and all. An unset item merged in on its own is dropped.\n  names <- [\"ann\", \"bob\"]\n  names :~: [\"cat\", \"ann\"]\n  stdout.println($names)\n\nSET\nThe source's items are added unless already present, so a shared item appears once.\n\nINSIDE A DICT\nA List held in a Dict is merged into, so the Lists under a shared key append (see Q1408).\n\nSee Q1405 for the rule on single values. See Q1081 for merging Lists. See Q1365 for Sets.","ek9Example":"#!ek9\ndefines module qa.collections.mergelistvsset\n\n  defines program\n\n    MergeListVsSetDemo()\n      stdout <- Stdout()\n\n      //A List appends, in order, duplicates and all\n      names <- [\"ann\", \"bob\"]\n      names :~: [\"cat\", \"ann\"]\n      stdout.println(`list: ${names}`)\n\n      //A Set takes the union\n      unique <- Set() of String\n      unique += \"ann\"\n      unique += \"bob\"\n\n      others <- Set() of String\n      others += \"bob\"\n      others += \"cat\"\n\n      unique :~: others\n      stdout.println(`set size: ${length unique}`)\n      stdout.println(`set holds cat: ${unique contains \"cat\"}`)","migrationContext":"Java: list.addAll(other) appends; set.addAll(other) is the union. Python: list.extend and set.update (|=). EK9: one operator for both - ':~:' appends a List and unions a Set.","keywords":[":~:","append","duplicates","list","list merge order","merge","union"],"primaryTopics":["merge list append","merge set union"],"typicalErrors":[],"companions":[]}
{"id":1410,"category":"Operators and Expressions","question":"How do I merge so that the newer value wins, when ':~:' always keeps the value already set?","url":"https://ek9.io/qa/QA1410.html","alternatePhrasings":["Can I make ':~:' overwrite existing values?","How do I merge two Dicts where the second one wins?","How do I add numbers together when merging instead of keeping the first?","What do I use when the built-in merge rule is not what I need?"],"answer":"Write your own function. ':~:' means one thing on every built-in type - it never overwrites a set value and fills whatever is unset or missing - and that is not configurable. When you need a different policy, say so in a function whose name states it.\n\nNEWER WINS FOR A WHOLE VALUE\nFor a single value use ':=:' (copy) or ':^:' (replace) - they overwrite.\n\nNEWER WINS, KEY BY KEY\nWalk the source and assign each entry. For a Dict, 'dict += DictEntry(key, value)' replaces the value under an existing key:\n  newerWins()\n    ->\n      target as Dict of (String, Integer)\n      source as Dict of (String, Integer)\n    for entry in source\n      target += entry\n\nCOMBINE VALUES\nTo add numbers rather than keep the first, stream them: 'cat values > total' sums, because streaming into a value is a fold, not a merge.\n\nSee Q1405 for the merge rule on single values. See Q1406 for gathering partial results with ':~:'. See Q1058 for copy, replace and merge.","ek9Example":"#!ek9\ndefines module qa.operators.mergecustombehaviour\n\n  defines function\n\n    newerWins()\n      ->\n        target as Dict of (String, Integer)\n        source as Dict of (String, Integer)\n      for entry in source\n        target += entry\n\n  defines program\n\n    MergeCustomBehaviourDemo()\n      stdout <- Stdout()\n\n      current <- {\"cpu\": 1, \"disk\": 7}\n      update <- {\"cpu\": 4, \"ram\": 8}\n\n      //The built-in merge keeps what is already set\n      merged <- {\"cpu\": 1, \"disk\": 7}\n      merged :~: update\n      stdout.println(`merge keeps: ${merged}`)\n\n      //A named function says the newer value wins\n      newerWins(current, update)\n      stdout.println(`newer wins: ${current}`)\n\n      //Streaming into a value folds - here it sums\n      total <- Integer()\n      cat [4, 8, 3] > total\n      stdout.println(`summed: ${total}`)","migrationContext":"Java: Map.merge(key, value, remappingFunction) lets every call choose its own policy. Python: dict.update lets the newer win. EK9: ':~:' has one fixed meaning (first recorded wins, fill the gaps); any other policy is an ordinary, named function.","keywords":[":~:","custom merge","merge","merge policy","newer wins","overwrite","remapping function"],"primaryTopics":["custom merge policy","merge newer wins"],"typicalErrors":[],"companions":[]}
{"id":1411,"category":"JSON and Data Processing","question":"How do I collect the items of a stream into a JSON array?","url":"https://ek9.io/qa/QA1411.html","alternatePhrasings":["What does 'collect as JSON' give me?","What happens when I stream items into an empty JSON?","How do I append streamed items to an existing JSON array?","Does pouring an array into a JSON array spread its elements?"],"answer":"Pour the stream into the JSON - 'collect as JSON', or '>' into a JSON you already have. Pouring into JSON follows the target's shape: an unset or array target collects, an object target merges each streamed object in; anything else - a non-object into an object, or anything into a single value - is invalid, leaves the target unset, and later items do not revive it.\n\nAN UNSET TARGET COLLECTS\nAn unset JSON has no shape yet, so it becomes an array of the items - objects included, and one item still gives an array of one:\n  collected <- cat [JSON(1), JSON(\"two\"), JSON(\"name\", JSON(\"Ann\"))] | collect as JSON\n  stdout.println($collected)\n\nAN ARRAY TARGET APPENDS EACH ITEM\nEvery streamed item becomes ONE element, so an array poured in is appended whole, not spread into its elements:\n  numbers <- $$ [0]\n  cat [JSON(1), $$ [2, 3]] > numbers\n  stdout.println($numbers)\nTo spread an array's elements into another array, merge it instead: numbers :~: other appends its elements.\n\nUNSET ITEMS ARE DROPPED\nLike every '|' an unset item is dropped, and a JSON null counts as unset, so it is dropped too.\n\nSee Q1412 for pouring objects into an object. See Q1413 for a target that ends up unset. See Q191 for pouring JSON into other types.","ek9Example":"#!ek9\ndefines module qa.jsonanddata.jsonpourcollects\n\n  defines program\n\n    JsonPourCollectsDemo()\n      stdout <- Stdout()\n\n      //An unset target has no shape yet: it collects the items into an array, objects too\n      collected <- cat [JSON(1), JSON(\"two\"), JSON(\"name\", JSON(\"Ann\"))] | collect as JSON\n      stdout.println(`collected: ${collected}`)\n\n      //One item still gives an array of one\n      single <- JSON()\n      cat [JSON(7)] > single\n      stdout.println(`one item: ${single}`)\n\n      //An array target appends each item as ONE element - the array [2, 3] is not spread\n      numbers <- $$ [0]\n      cat [JSON(1), $$ [2, 3]] > numbers\n      stdout.println(`appended: ${numbers}`)\n\n      //An unset item is dropped (so is a JSON null, which is unset)\n      withUnset <- $$ [1]\n      cat [JSON(), JSON(2)] > withUnset\n      stdout.println(`unset dropped: ${withUnset}`)","migrationContext":"Java: Jackson ArrayNode.add() in a loop, or stream().collect() into a List and then valueToTree. Python: json.dumps([item for item in items]). JavaScript: items.map(...) then JSON.stringify. EK9: cat items | collect as JSON gathers JSON items into an array, and '>' appends them to an existing array, each item one element.","keywords":["append","array","collect","gather","json","pipe","pour","stream","unset"],"primaryTopics":["collect as JSON","stream into json array"],"typicalErrors":[],"companions":[]}
{"id":1412,"category":"JSON and Data Processing","question":"How do I combine a stream of partial JSON objects into one JSON object?","url":"https://ek9.io/qa/QA1412.html","alternatePhrasings":["What happens when I stream JSON objects into a JSON object?","Which value wins when two JSON objects streamed into one have the same key?","Does pouring JSON objects into an object merge nested objects?","How do I gather partial JSON results from several sources into one object?"],"answer":"Pour them into a JSON object with '>'. Pouring into JSON follows the target's shape: an unset or array target collects, an object target merges each streamed object in; anything else - a non-object into an object, or anything into a single value - is invalid, leaves the target unset, and later items do not revive it.\n\nAN OBJECT TARGET MERGES\nEach streamed object is merged in by the merge rule (the same rule as ':~:'):\n- a key the target lacks, or holds as null, is filled;\n- a key the target already holds is kept - the first recorded value wins;\n- a nested object under a shared key is merged by this same rule, and a nested array appends.\nWhat the target takes is a copy, so changing a source afterwards does not reach it.\n\nSTART FROM AN OBJECT\nThe target must already be an object - an unset JSON collects the items into an array instead (Q1411). JSON().object() is an empty object:\n  combined <- JSON().object()\n  cat [JSON(\"name\", JSON(\"Ann\")), JSON(\"name\", JSON(\"Bob\"))] > combined\n  stdout.println($combined)\n\nSee Q1411 for collecting into an array. See Q1413 for a stream that leaves the object unset. See Q192 for ':~:' on JSON.","ek9Example":"#!ek9\ndefines module qa.jsonanddata.jsonpourmergesobjects\n\n  defines program\n\n    JsonPourMergesObjectsDemo()\n      stdout <- Stdout()\n\n      //The first recorded value wins on a clashing key\n      firstWins <- JSON().object()\n      cat [JSON(\"name\", JSON(\"Ann\")), JSON(\"name\", JSON(\"Bob\"))] > firstWins\n      stdout.println(`first wins: ${firstWins}`)\n\n      //Nested objects merge by the same rule, nested arrays append, missing keys are filled\n      person <- JSON(\"name\", JSON(\"Ann\")) + JSON(\"address\", JSON(\"city\", JSON(\"Leeds\"))) + JSON(\"tags\", $$ [\"a\"])\n      fromTaskOne <- JSON(\"name\", JSON(\"Bob\")) + JSON(\"address\", JSON(\"city\", JSON(\"York\")) + JSON(\"zip\", JSON(\"LS1\")))\n      fromTaskTwo <- JSON(\"tags\", $$ [\"b\"]) + JSON(\"age\", JSON(30))\n      cat [fromTaskOne, fromTaskTwo] > person\n      stdout.println(`combined: ${person}`)","migrationContext":"Java: Jackson ObjectNode.setAll() overwrites (last wins); a first-wins deep merge is hand-written. Python: {**a, **b} and dict.update() are shallow and last-wins. JavaScript: Object.assign is shallow and last-wins; lodash.merge is deep but last-wins. EK9: '>' into a JSON object is a deep merge where the first recorded value wins, so partial results gathered from several sources never overwrite each other.","keywords":["combine","first wins","json","merge","nested","object","partial","pipe","pour","stream"],"primaryTopics":["stream json objects into an object","combine partial json"],"typicalErrors":[],"companions":[]}
{"id":1413,"category":"JSON and Data Processing","question":"Why is my JSON object unset after I streamed values into it?","url":"https://ek9.io/qa/QA1413.html","alternatePhrasings":["What happens when I stream a number into a JSON object or a JSON value?","Can I pour a JSON array into a JSON object?","How do I stop non-objects spoiling a JSON object I stream into?","Why does json? stay false for the rest of a pipeline?"],"answer":"A JSON object can only take objects, and a single JSON value can take nothing. Pouring into JSON follows the target's shape: an unset or array target collects, an object target merges each streamed object in; anything else - a non-object into an object, or anything into a single value - is invalid, leaves the target unset, and later items do not revive it.\n\nA MISMATCH IS INVALID\nA scalar (a number, a String, a Boolean) or an array poured into an object has no key to merge under, so the operation is invalid. A single value has a nature and is not a container, so pouring anything into it is invalid too. EK9's answer to an invalid operation is UNSET - no exception, no half-merged object.\njson? tells you whether the target survived:\n  settings <- JSON(\"theme\", JSON(\"dark\"))\n  cat [JSON(7)] > settings\n  stdout.println(`still set: ${settings?}`)\n\nTHE MISMATCH STICKS\nOnce a pour has gone wrong, the items after it are ignored - an object poured in after the mismatch does not start a new array - so a mismatch anywhere in the stream is still visible at the end. Only an explicit assignment ends it: ':=:', ':~:' (which takes its source, as it does into any unset JSON) or clear().\n\nKEEP THE STREAM TO OBJECTS\nFilter the stream down to objects before it reaches an object target:\n  isObject() as pure\n    -> item as JSON\n    <- rtn as Boolean: item.objectNature()\nthen cat items | filter by isObject > settings.\n\nTo collect values of any shape, pour them into an unset JSON or an array instead (Q1411). See Q1412 for merging objects.","ek9Example":"#!ek9\ndefines module qa.jsonanddata.jsonpourmismatchunset\n\n  defines function\n\n    isObject() as pure\n      -> item as JSON\n      <- rtn as Boolean: item.objectNature()\n\n  defines program\n\n    JsonPourMismatchUnsetDemo()\n      stdout <- Stdout()\n\n      //A scalar has no key to merge under: the object is left unset\n      scalarInto <- JSON(\"colour\", JSON(\"red\"))\n      cat [JSON(7)] > scalarInto\n      stdout.println(`scalar into object, still set: ${scalarInto?}`)\n\n      //So does an array\n      arrayInto <- JSON(\"font\", JSON(\"serif\"))\n      cat [$$ [1, 2]] > arrayInto\n      stdout.println(`array into object, still set: ${arrayInto?}`)\n\n      //A single value has a nature and is not a container: nothing can be poured into it\n      count <- JSON(1)\n      cat [JSON(2)] > count\n      stdout.println(`into a single value, still set: ${count?}`)\n\n      //The mismatch sticks: an object after it does not start a new array\n      sticky <- JSON(\"size\", JSON(10))\n      cat [JSON(7), JSON(\"weight\", JSON(3))] > sticky\n      stdout.println(`object after the mismatch, still set: ${sticky?}`)\n\n      //Keep the stream to objects before it reaches an object target\n      settings <- JSON(\"theme\", JSON(\"dark\"))\n      cat [JSON(7), JSON(\"size\", JSON(12)), $$ [1, 2]] | filter by isObject > settings\n      stdout.println(`filtered: ${settings}`)","migrationContext":"Java: Jackson ObjectNode.setAll() only accepts an ObjectNode, so a value of the wrong shape is a compile error or a ClassCastException. JavaScript: Object.assign({}, 7) silently ignores the number. EK9: a value of the wrong shape poured into a JSON object, or anything poured into a single JSON value, makes it unset for the rest of the pour, visible with json?, and a filter keeps the stream to objects.","keywords":["filter","invalid","json","mismatch","object","objectNature","pipe","pour","stream","unset"],"primaryTopics":["json object unset after stream","stream scalar into json object"],"typicalErrors":[],"companions":[]}
{"id":1431,"category":"Error Handling and Exceptions","question":"How does an EK9 program set its exit status?","url":"https://ek9.io/qa/QA1431.html","alternatePhrasings":["How do I return a non-zero exit code from an EK9 program?","What exit code does my EK9 program end with?","Is there a System.exit or sys.exit in EK9?","How do I make my program fail a shell script or CI step?"],"answer":"An EK9 program ends with exactly one exit status, and only four things decide it. There is no exit() call to reach for in the middle of the code.\n\n1. A NORMAL END is 0. A program that declares no return and finishes exits 0.\n\n2. A RETURNED Integer is the status. A program may declare a return, and it must be an Integer (anything else is E07590). The value it holds when the program finishes is the exit status, and an UNSET return exits 1. The application teardown runs first, and then the program exits with that status. The program below ends with 'exitCode: invalid > 0 <- 2 : 0', so it exits 2 when it finds invalid input and 0 when it does not.\n\n3. An Exception NOTHING CATCHES ends the program with that exception's own exit code, or 1 when it has none. Exception(\"No settings file named\", 3) exits 3. The finally blocks and the application teardown run first. See Q1432 and Q1433.\n\n4. A Panic always exits 1. A Panic is raised only by the language: a failed require, a constrained-type value out of range, or an unset for-range bound. You cannot throw one or choose its code. See Q1434 and Q1435.\n\nWHICH ONE TO USE\nReturn an Integer when the program finishes its work and reports an outcome, such as 2 for 'some records were invalid'. Throw an Exception with a code, and do not catch it, when the program cannot go on and you want the caller to see a particular failure status. Use require for programming errors. Every require failure exits 1, so it is not a way to report an outcome.\n\nTHE ek9 COMMAND HAS ITS OWN CODES\nCompiling can fail before your program ever runs, and the ek9 command then exits with its own code, for example 8 when compilation fails. Your program's codes share those numbers. See Q1437 for how to tell them apart.\n\nSee Q888 for why a program may only return an Integer. See Q136 for throwing exceptions.","ek9Example":"#!ek9\ndefines module qa.errorhandling.programexitstatus\n\n  defines function\n\n    countInvalid()\n      -> quantities as List of Integer\n      <- rtn as Integer: 0\n      for quantity in quantities\n        if quantity < 0\n          rtn++\n\n  defines program\n\n    ExitStatusDemo()\n      <- exitCode as Integer?\n      stdout <- Stdout()\n\n      invalid <- countInvalid([3, 7, 12])\n      stdout.println(`Invalid quantities: ${invalid}`)\n\n      exitCode: invalid > 0 <- 2 : 0\n      stdout.println(`Exiting with status: ${exitCode}`)","migrationContext":"Java: System.exit(n) from anywhere, or an uncaught exception exits 1. C: return n from main, or exit(n). Python: sys.exit(n) raises SystemExit. Go: os.Exit(n), which skips deferred calls. Rust: std::process::exit(n), or main returns Result. EK9: no exit call at all. The status comes from the program's return value, the exit code of an uncaught Exception, or a Panic (always 1), and finally blocks and teardown always run first.","keywords":["CI","Integer","System.exit","exit","exit code","exit status","program","return code","shell","sys.exit"],"primaryTopics":["program exit status","exit code"],"typicalErrors":[{"error":"E07590","correct":"      <- exitCode as Integer?","incorrect":"      <- exitCode as Boolean?","explanation":"A program's return value is its exit status, so it can only be an Integer. Return 0 for success and a non-zero Integer for a failure you want the caller to see. See ek9 -h E07590 for details."}],"companions":[]}
{"id":1432,"category":"Error Handling and Exceptions","question":"How do I end my program with a chosen exit code when something fails?","url":"https://ek9.io/qa/QA1432.html","alternatePhrasings":["What happens to the exit code of an Exception nothing catches?","How do I exit with code 3 when a configuration file is missing?","Does Exception(reason, exitCode) really set the process exit status?","How do I stop an EK9 program with a specific error code?"],"answer":"Throw an Exception that carries the exit code, and do not catch it. When it reaches the top of the program, the program prints it on stderr, runs every finally block and the application teardown, and then exits with that code.\n\n  throw Exception(\"No settings file named\", 3)\n\nThe program below exits 3. The 'Cleanup ran' line is printed by a finally block, so cleanup happens before the process ends. stderr gets one line, with no Java stack trace: 'Exception in thread \"main\" Exception: No settings file named: Exit Code: 3'.\n\nRULES\n- The code is the one on the exception that reaches the top. If it has no code, the program exits 1 (see Q1433).\n- A catch that handles the exception ends that failure, so its code no longer applies. To keep the code, let the exception pass or throw it again (see Q1436).\n- A subclass of Exception passes the code to super(reason, exitCode), and it behaves the same way.\n- You cannot use Panic for this. User code cannot create a Panic - it has no constructors you can call - so 'throw Panic(...)' is E50060, and a Panic always exits 1 (see Q1434).\n\nWhen the run is started by the ek9 command, a status from your program is the program's own and not the compiler's (see Q1437).\n\nSee Q1431 for every way a program sets its exit status. See Q136 for throwing exceptions.","ek9Example":"#!ek9\ndefines module qa.errorhandling.uncaughtexitcode\n\n  defines function\n\n    loadSettings()\n      -> fileName as String\n      <- rtn as String?\n      if not fileName?\n        throw Exception(\"No settings file named\", 3)\n      rtn: `Loaded ${fileName}`\n\n  defines program\n\n    UncaughtExitCodeDemo()\n      stdout <- Stdout()\n      stdout.println(loadSettings(\"app.properties\"))\n\n      try\n        stdout.println(\"Loading settings with no file name\")\n        stdout.println(loadSettings(String()))\n      finally\n        stdout.println(\"Cleanup ran before the process ended\")","migrationContext":"Java: an uncaught exception always exits 1 whatever it carries, so a chosen code needs System.exit(n). Python: raise SystemExit(3). Rust: std::process::exit(3) skips destructors. EK9: Exception(\"reason\", 3) that nothing catches exits 3, after finally blocks and teardown.","keywords":["exception","exit code","exitCode","finally","process status","stderr","teardown","throw","uncaught"],"primaryTopics":["uncaught exception exit code","chosen exit code"],"typicalErrors":[{"error":"E50060","correct":"        throw Exception(\"No settings file named\", 3)","incorrect":"        throw Panic(\"No settings file named\", 3)","explanation":"Panic is raised only by the language and has no constructors user code can call, so 'Panic(...)' does not resolve - and it is not an Exception, so it could not be thrown anyway. An uncaught Exception carrying the code ends the program with that code. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1433,"category":"Error Handling and Exceptions","question":"What exit code does an uncaught Exception with no exit code give, and what about one that wraps a cause?","url":"https://ek9.io/qa/QA1433.html","alternatePhrasings":["Why did my program exit 1 when the inner exception had code 4?","Does a wrapping exception inherit its cause's exit code?","What is the default exit code of an uncaught Exception?"],"answer":"An uncaught Exception with no exit code ends the program with status 1. Only the exception that reaches the top counts. A wrapping exception does NOT inherit its cause's code, even when the cause has one.\n\nIn the program below, connect() throws Exception(... , 4). startService() catches it and throws a new Exception(\"Service failed to start\", ex) with no code of its own. The process exits 1, not 4. The cause still appears in the stderr line: 'Exception in thread \"main\" Exception: Service failed to start: Root Cause: Exception: Cannot reach db.example.com: Exit Code: 4'. The 'Exit Code: 4' in that line belongs to the cause. The status is 1.\n\nTO KEEP A CODE WHEN WRAPPING\nGive the wrapper its own code: Exception(\"Service failed to start\", ex, 4) exits 4. The code is chosen by whoever throws last, because only that code describes the failure the program is reporting.\n\nSee Q1432 for an exception that carries its code. See Q1436 for catching and throwing again. See Q1431 for every way a program sets its exit status.","ek9Example":"#!ek9\ndefines module qa.errorhandling.uncaughtnocode\n\n  defines function\n\n    connect()\n      -> host as String\n      throw Exception(`Cannot reach ${host}`, 4)\n\n    startService()\n      try\n        connect(\"db.example.com\")\n      catch\n        -> ex as Exception\n        throw Exception(\"Service failed to start\", ex)\n\n  defines program\n\n    UncaughtNoCodeDemo()\n      stdout <- Stdout()\n      stdout.println(\"Starting service\")\n      startService()","migrationContext":"Java: every uncaught exception exits 1. Python: an uncaught exception exits 1. EK9: an uncaught Exception exits with its OWN exit code, or 1 when it has none. A cause's code is never used.","keywords":["cause","default","exception chaining","exit code","root cause","uncaught","wrap"],"primaryTopics":["uncaught exception default exit code","wrapped exception exit code"],"typicalErrors":[],"companions":[]}
{"id":1434,"category":"Error Handling and Exceptions","question":"What is a Panic in EK9 and when does the language raise one?","url":"https://ek9.io/qa/QA1434.html","alternatePhrasings":["Why can't my catch block catch a Panic?","Can I throw a Panic myself?","What exit code does a Panic give?","What does 'Exception in thread main Panic: Constraint violation' mean?"],"answer":"A Panic is the language reporting a broken rule that the program's own logic should have prevented. Only the language raises one. User code cannot create, throw or catch a Panic, and it always ends the program with exit status 1.\n\nWHEN THE LANGUAGE RAISES A PANIC\n- A require condition is false or unset (see Q1435).\n- A constrained type is given a value outside its constraint at run time. Percentage(150) below is one.\n- A for-range loop is given an unset start or end.\n\nWHY A catch DOES NOT SEE IT\nPanic is not a subtype of Exception, and a catch can only name an Exception, so no catch matches a Panic. 'catch -> ex as Panic' is E04030. In the program below the catch never runs. The finally block still runs, so cleanup and teardown happen. Then the program prints one line on stderr, 'Exception in thread \"main\" Panic: Constraint violation for qa.errorhandling.whatispanic::Percentage: Exit Code: 1', and exits 1.\n\nWHY YOU CANNOT CREATE OR THROW ONE\nPanic has no constructors user code can call, so 'Panic(...)' - with any arguments, or none - is E50060, and so is 'throw Panic(...)'. Even if you had one, only an Exception can be thrown. To end a program with a failure status you choose, throw an Exception with an exit code and do not catch it (see Q1432). The exit code of a Panic is always 1.\n\nCONVERTING UNTRUSTED VALUES WITHOUT A PANIC\nPercentage().of(score) gives an UNSET Percentage instead of a Panic when the value is out of range, and you check it with '?'. Use the bare constructor only when an out-of-range value would be a bug.\n\nIN TESTS\nassertPanic in a @Test is the one way to observe a Panic (see Q1435).\n\nSee Q1431 for every way a program sets its exit status. See Q304 for require.","ek9Example":"#!ek9\ndefines module qa.errorhandling.whatispanic\n\n  defines type\n\n    Percentage as Integer constrain as\n      >= 0 and <= 100\n\n  defines function\n\n    toPercentage()\n      -> score as Integer\n      <- rtn as Percentage: Percentage(score)\n\n  defines program\n\n    PanicDemo()\n      stdout <- Stdout()\n      stdout.println(`In range: ${toPercentage(Integer(\"42\"))}`)\n\n      try\n        stdout.println(\"Converting 150\")\n        stdout.println($toPercentage(Integer(\"150\")))\n      catch\n        -> ex as Exception\n        stdout.println(`Caught: ${ex}`)\n      finally\n        stdout.println(\"finally still runs\")","migrationContext":"Go: panic() can be called by user code and recover() can stop it. Rust: panic!() is user-callable, and catch_unwind can catch it in some cases. Java: Error subclasses can still be caught. EK9: Panic is raised only by the language, no catch can see it, finally blocks still run, and the program exits 1.","keywords":["catch","constraint violation","exit code 1","fatal","panic","require","uncatchable"],"primaryTopics":["Panic","uncatchable failure"],"typicalErrors":[{"error":"E04030","correct":"        -> ex as Exception","incorrect":"        -> ex as Panic","explanation":"A catch can only name an Exception or a subtype of it. Panic is not an Exception, so it cannot be caught. Prevent the Panic instead, for example with Percentage().of(score) and a '?' check. See ek9 -h E04030 for details."}],"companions":[]}
{"id":1435,"category":"Error Handling and Exceptions","question":"What exit code does a failed require or assert give?","url":"https://ek9.io/qa/QA1435.html","alternatePhrasings":["Can I set the exit code of a failed require?","What happens to the process when a require fails?","Why did my program exit 1 with 'Requirement failed'?","What exit code does a failed assert give?"],"answer":"A failed require always ends the program with exit status 1. The language raises a Panic: a catch cannot see it, the finally blocks and the application teardown still run, and stderr gets one line that names the condition and where it is: 'Exception in thread \"main\" Panic: Requirement failed: `not scores.empty()` at <file>:<line>:<column>: Exit Code: 1'.\n\nThe code is always 1, so a require is not a way to report an outcome. It says the program has a bug. To end with a status you choose, throw an Exception with an exit code and do not catch it (see Q1432). For an outcome the program expects, return an Integer from the program (see Q1431).\n\nAND assert?\nassert is only allowed in dev source files (E81015 anywhere else), so a production program never contains one. Under 'ek9 -t' a false assert fails that test. The test runner reports it, and the ek9 command exits 11 ('tests failed'). That 11 is the ek9 command's own code, not a status from your program (see Q1437). In a test, assertPanic(expression) checks that a require really fails.\n\nGUARD INSTEAD OF FAILING\nIf an empty list is a normal case, it is not a require. Check it first and choose what happens:\n  if scores.empty()\n    stdout.println(\"No scores yet\")\n\nSee Q304 for require. See Q305 for require vs assert vs throw. See Q1434 for Panic.","ek9Example":"#!ek9\ndefines module qa.errorhandling.requireexitone\n\n  defines function\n\n    averageOf()\n      -> scores as List of Integer\n      <- rtn as Integer?\n      require not scores.empty()\n      total <- Integer()\n      cat scores > total\n      rtn: total / length scores\n\n  defines program\n\n    RequireExitDemo()\n      stdout <- Stdout()\n      stdout.println(`Average: ${averageOf([4, 6, 8])}`)\n\n      noScores <- List() of Integer\n      stdout.println(\"Averaging an empty list\")\n      averageOf(noScores)","migrationContext":"Java: a failed assert throws AssertionError, but only when -ea is on, and the program exits 1. Go: panic exits 2. Rust: panic! exits 101. EK9: a failed require always runs, cannot be caught, and exits 1. assert lives only in dev sources and fails a test (ek9 -t exits 11).","keywords":["Requirement failed","assert","exit 1","exit code","panic","precondition","require","test failure"],"primaryTopics":["require failure exit code","assert exit code"],"typicalErrors":[{"error":"E81015","correct":"      require not scores.empty()","incorrect":"      assert not scores.empty()","explanation":"assert is a test construct and is only allowed in dev source files. A precondition in production code is a require, which fails with exit status 1. See ek9 -h E81015 for details."}],"companions":[]}
{"id":1436,"category":"Error Handling and Exceptions","question":"Should I catch an Exception that carries an exit code, or let it end the program?","url":"https://ek9.io/qa/QA1436.html","alternatePhrasings":["Why did my program exit 0 after I caught the exception?","How do I log an exception and still exit with its code?","How do I rethrow an exception in EK9?","Does catching an exception lose its exit code?"],"answer":"It depends on whether the program can carry on.\n\nCATCH IT when the program can recover. The catch handles the failure, the program continues, and the exception's exit code no longer applies. In the program below, the first read fails with code 4, the catch recovers, and the program goes on.\n\nLET IT PASS when the program cannot recover. An Exception nothing catches ends the program with its own exit code, after the finally blocks and teardown (see Q1432).\n\nCATCH, LOG, AND THROW IT AGAIN when you want both. Throw the same exception from the catch and its code is kept. The second try below logs the failure and throws ex again, so the program exits 4:\n  catch\n    -> ex as Exception\n    stdout.println(`Logging, then passing it on: ${ex.reason()}`)\n    throw ex\n\nTHE TRAPS\n- Catching an exception and only printing it ends the failure, and the program then exits 0. A CI step that runs it will not see a failure.\n- Throwing a NEW exception without a code exits 1, even if the caught one had a code (see Q1433). To keep the code, give the new exception one too: Exception(\"Import failed\", ex, 4).\n- Throwing the message instead of the exception, 'reason <- ex.reason()' then 'throw reason', is E04030. Only an Exception can be thrown, never a String.\n\nSee Q134 for try/catch. See Q135 for finally. See Q1431 for every way a program sets its exit status.","ek9Example":"#!ek9\ndefines module qa.errorhandling.catchorexit\n\n  defines function\n\n    readOrders()\n      -> source as String\n      <- rtn as Integer: 0\n      if source <> \"orders.csv\"\n        throw Exception(`Cannot read ${source}`, 4)\n      rtn: 12\n\n  defines program\n\n    CatchOrExitDemo()\n      stdout <- Stdout()\n\n      try\n        stdout.println(`Orders: ${readOrders(\"backup.csv\")}`)\n      catch\n        -> ex as Exception\n        stdout.println(`Recovered from: ${ex.reason()}`)\n\n      try\n        stdout.println(`Orders: ${readOrders(\"orders.csv\")}`)\n        readOrders(\"archive.csv\")\n      catch\n        -> ex as Exception\n        stdout.println(`Logging, then passing it on: ${ex.reason()}`)\n        throw ex","migrationContext":"Java: catch (E e) { log(e); throw e; } keeps the exception, but the JVM exits 1 whatever it carries. Python: a bare 'raise' re-raises. EK9: 'throw ex' inside the catch throws the same exception again, and its exit code becomes the program's status if nothing else catches it.","keywords":["catch","exit code","log and rethrow","propagate","recover","rethrow","throw ex"],"primaryTopics":["rethrow exception","catch vs let it exit"],"typicalErrors":[{"error":"E04030","correct":"        throw ex","incorrect":"        reason <- ex.reason()\n        throw reason","explanation":"Only an Exception can be thrown, and ex.reason() gives a String. Throw ex itself to pass the failure on with its exit code. See ek9 -h E04030 for details."}],"companions":[]}
{"id":1437,"category":"Error Handling and Exceptions","question":"My ek9 command exited 3 - was that the compiler or my program?","url":"https://ek9.io/qa/QA1437.html","alternatePhrasings":["How do I tell an EK9 compile failure from my program's own exit code?","What is the difference between the ek9 CLI exit codes and my program's exit codes?","Why does 'ek9 myprogram.ek9' return my program's exit code?","What does the 'ek9: ... (exit N)' line on stderr mean?"],"answer":"Look at stderr, because the number alone cannot tell you. When you run 'ek9 myprogram.ek9' (or ./myprogram.ek9), there are two stages, and each can end the run with a status:\n\n1. THE ek9 COMMAND compiles your source. If that fails, ek9 exits with ITS OWN code and your program never runs: 3 when a file cannot be read, 8 when compilation fails, 9 or 10 when the program arguments do not fit, and so on (the full table is in 'ek9 -h' and on the command line page).\n\n2. YOUR PROGRAM runs, and the ek9 command passes your program's exit status through unchanged. That is 0, a returned Integer, an uncaught Exception's code, or 1 for a Panic (see Q1431).\n\nThe two share one set of numbers. The program below exits 3 because the input has no rows. 3 is also the ek9 command's 'file access error' code, but here the compiler did nothing wrong.\n\nHOW TO TELL THEM APART\n- The stderr text says which one it was. An uncaught Exception or Panic from your program prints 'Exception in thread \"main\" ...' on stderr, and this program prints 'Exception in thread \"main\" Exception: Input has no rows: Exit Code: 3'. A compile failure prints EK9 error lines instead.\n- When the ek9 command ITSELF fails, its first stderr line is 'ek9: <reason> (exit N)', for example 'ek9: compilation failed (exit 8)'. Your program's exit never carries that line, so a non-zero status without it is your program's own.\n- Compile separately with 'ek9 -C myprogram.ek9' before you run anything. Its non-zero exit is always the compiler's.\n- Choose your own program's codes and document them. Q1432 shows how to exit with one.\n\nSee Q1431 for every way a program sets its exit status. See Q888 for program return values.","ek9Example":"#!ek9\ndefines module qa.errorhandling.cliversusprogram\n\n  defines function\n\n    rowCount()\n      -> rows as List of String\n      <- rtn as Integer: length rows\n\n  defines program\n\n    ImportRows()\n      stdout <- Stdout()\n      rows <- List() of String\n      count <- rowCount(rows)\n      stdout.println(`Rows found: ${count}`)\n      if count == 0\n        throw Exception(\"Input has no rows\", 3)\n      stdout.println(\"Import complete\")","migrationContext":"Java: javac and java are separate commands, so a compile failure and a program exit code never meet. Go: 'go run' prints 'exit status N' for the program's code, while a build failure exits 1. EK9: one command compiles and runs, so the stderr text (and the 'ek9: ... (exit N)' marker) is what tells the stages apart.","keywords":["CI","CLI","compile failed","ek9 command","exit code","program exit code","shell script","stderr","wrapper"],"primaryTopics":["CLI vs program exit codes","ek9 command exit codes"],"typicalErrors":[],"companions":[]}
{"id":1471,"category":"Classes and OOP","question":"How can an object hold a reference to itself, like a doubly linked node that starts as a ring of one?","url":"https://ek9.io/qa/QA1471.html","alternatePhrasings":["Can a constructor assign 'this' to one of the object's own properties?","How do I write 'me: this' in an EK9 constructor without E08061?","How do I build a circular linked list node whose next and previous point to itself?","Why is reading through a property set to 'this' in the constructor still E08061?"],"answer":"A property of the object's own type may refer back to the object: assign 'this' to it in the constructor, with ':', ':=', '=' or ':=?' ('prev: this', 'this.owner :=? this'). Storing the object in one of its OWN properties is not a use of it - nothing is read through it and nothing outside the object holds it - so it sets that property like any other assignment, even while other properties are still unset. A doubly linked node can start as a ring of one before its label is set:\n\n  Node\n    prev as Node?\n    next as Node?\n    label as String?\n\n    default private Node()\n\n    Node()\n      -> text as String\n      prev: this\n      next: this\n      label: text\n\n    label()\n      <- rtn as String: label\n\n    override operator ? as pure\n      <- rtn as Boolean: label?\n\nTHE PROPERTY IS THE OBJECT UNDER ANOTHER NAME\nOnly 'this' itself may be stored that way, and in that constructor the property then IS the object. Calling a method through it ('itself.doubled()'), copying it to a local, or reading 'this.itself' is a use of the object, so it needs every property set first, exactly as a method call on 'this' does (E08061; see Q1396). Passing it to another object's method is publishing a half-built object (E08250), as passing 'this' is. Once every property is set it can be used freely. The same store one step removed - a child created with a capture of 'this' and kept in the object's own property ('section :=? (owner: this) with trait of Part as class ...') - is the parent link: see Q2181.\n\nGENERATED OPERATORS ARE SAFE ON A CYCLE\nA type like this can reach itself through its properties, and the compiler works that out. Its GENERATED operators ('default operator ?', '$', '$$', '==', '#?') then carry a cycle guard, so a property that refers back never recurses without end: '?' treats the object it is already checking as set; '$' and '$$' on a cycle are unset (never JSON with a hole where the loop was); '==' assumes a pair it is already comparing is equal, so two identical rings are equal; and '#?' leaves out the properties that lead back. A type that cannot reach itself pays nothing. The node above still writes its own '?', because only its label says whether it is set.","ek9Example":"#!ek9\ndefines module qa.oop.property.refers.to.itself\n\n  defines class\n\n    Node\n      prev as Node?\n      next as Node?\n      label as String?\n\n      default private Node()\n\n      Node()\n        -> text as String\n        prev: this\n        next: this\n        label: text\n\n      label()\n        <- rtn as String: label\n\n      next()\n        <- rtn as Node: next\n\n      prev()\n        <- rtn as Node: prev\n\n      linkAfter()\n        -> node as Node\n        follower <- next\n        node.joinBetween(this, follower)\n        follower.joinPrev(node)\n        next: node\n\n      joinBetween()\n        ->\n          before as Node\n          after as Node\n        prev: before\n        next: after\n\n      joinPrev()\n        -> node as Node\n        prev: node\n\n      override operator ? as pure\n        <- rtn as Boolean: label?\n\n    Tally\n      itself as Tally?\n      count as Integer?\n      total as Integer: 0\n\n      default private Tally()\n\n      Tally()\n        -> start as Integer\n        itself: this\n        count: start\n        total: itself.doubled()\n\n      doubled()\n        <- rtn as Integer: count * 2\n\n      value()\n        <- rtn as Integer: total\n\n      override operator ? as pure\n        <- rtn as Boolean: total?\n\n  defines program\n\n    SelfReferenceDemo()\n      stdout <- Stdout()\n\n      first <- Node(\"first\")\n      stdout.println(`after first: ${first.next().label()}`)\n      stdout.println(`before first: ${first.prev().label()}`)\n\n      first.linkAfter(Node(\"second\"))\n      second <- first.next()\n      stdout.println(`ring: ${first.label()}, ${second.label()}, ${second.next().label()}`)\n      stdout.println(`back: ${first.prev().label()}`)\n\n      stdout.println($Tally(21).value())","migrationContext":"Java: 'this.next = this;' in a constructor compiles, and so does every use of a half-built object - the compiler checks neither. Swift: two-phase initialisation - 'self' cannot be used until every stored property is set, so a property that must point at 'self' is declared optional or implicitly unwrapped and set after. Kotlin: 'lateinit var next: Node' and 'next = this' in init, with a 'Leaking this' warning on any other use. EK9: storing 'this' in an own property is allowed at any point in the constructor; any read through that property waits for the object to be complete (E08061).","keywords":["E08061","E08250","circular","constructor","doubly linked","linked list","node","property","ring","self reference","self-referential"],"primaryTopics":["classes","constructors","E08061"],"typicalErrors":[{"error":"E08061","correct":"count: start\n        total: itself.doubled()","incorrect":"total: itself.doubled()\n        count: start","explanation":"'itself' was set to 'this', so calling doubled() through it is a call on the object - and doubled() reads 'count', which is not set yet. Storing 'this' is fine at any point; reading through it waits until every property is set."}],"companions":[]}
{"id":1480,"category":"Common String Operations","question":"Why is 'if name is empty' not true when the String is unset?","url":"https://ek9.io/qa/QA1480.html","alternatePhrasings":["My 'if name is empty' branch is skipped when no name was given - why?","Is an unset String empty in EK9?","How do I check that a String is missing or blank?","Why does 'is not empty' also skip its branch for an unset String?"],"answer":"An UNSET String is not empty - and it is not 'not empty' either. 'name is empty' on an unset String gives an UNSET Boolean, and an if whose condition is unset does not take its branch. So the branch below is skipped exactly when there is no name at all, which is usually the very case it was written for:\n\n  trapCheck()\n    -> name as String\n    <- rtn as String: \"has text, or the check was skipped\"\n    if name is empty\n      rtn := \"empty\"\n\ntrapCheck(String()) returns \"has text, or the check was skipped\". Writing 'is not empty' instead does not help: it is unset too, so that branch is skipped as well.\n\nWHY\nA value is absent, present but unset, or set. An operator on an unset value gives an unset result instead of guessing, and if, while and the ternary act only on a SET true. An empty String \"\" IS set - its value is known, it has no characters - so 'is empty' is true for it. Missing and blank are different answers, and EK9 keeps them apart.\n\nSAY WHICH ONE YOU MEAN\nMissing OR blank - test set-ness first:\n\n  blankOrMissing()\n    -> name as String\n    <- rtn as Boolean: not name? or name is empty\n\nMissing and blank handled differently - '?' first, then 'is empty':\n\n  describe()\n    -> name as String\n    <- rtn as String?\n    if not name?\n      rtn := \"missing\"\n    else if name is empty\n      rtn := \"empty\"\n    else\n      rtn := `text '${name}'`\n\nOnly real text - 'name? and name is not empty':\n\n  displayName()\n    -> name as String\n    <- rtn as String: \"(anonymous)\"\n    if name? and name is not empty\n      rtn := name\n\nThe program below runs each one on an unset String, on \"\" and on \"Alice\". A collection is never unset once it exists (see Q1019), but a String - a parameter, a field, a value read from input - can be.","ek9Example":"#!ek9\ndefines module qa.stringops.unsetisnotempty\n\n  defines function\n\n    trapCheck()\n      -> name as String\n      <- rtn as String: \"has text, or the check was skipped\"\n      if name is empty\n        rtn := \"empty\"\n\n    blankOrMissing()\n      -> name as String\n      <- rtn as Boolean: not name? or name is empty\n\n    describe()\n      -> name as String\n      <- rtn as String?\n      if not name?\n        rtn := \"missing\"\n      else if name is empty\n        rtn := \"empty\"\n      else\n        rtn := `text '${name}'`\n\n    displayName()\n      -> name as String\n      <- rtn as String: \"(anonymous)\"\n      if name? and name is not empty\n        rtn := name\n\n  defines program\n\n    UnsetIsNotEmptyDemo()\n      stdout <- Stdout()\n      missing <- String()\n      blank <- \"\"\n      alice <- \"Alice\"\n\n      stdout.println(`trap on unset: ${trapCheck(missing)}`)\n      stdout.println(`trap on blank: ${trapCheck(blank)}`)\n      stdout.println(`is empty on unset is set?: ${(missing is empty)?}`)\n      stdout.println(`is not empty on unset is set?: ${(missing is not empty)?}`)\n\n      stdout.println(`blankOrMissing unset: ${blankOrMissing(missing)}`)\n      stdout.println(`blankOrMissing blank: ${blankOrMissing(blank)}`)\n      stdout.println(`blankOrMissing text: ${blankOrMissing(alice)}`)\n\n      stdout.println(`describe unset: ${describe(missing)}`)\n      stdout.println(`describe blank: ${describe(blank)}`)\n      stdout.println(`describe text: ${describe(alice)}`)\n\n      stdout.println(`displayName unset: ${displayName(missing)}`)\n      stdout.println(`displayName blank: ${displayName(blank)}`)\n      stdout.println(`displayName text: ${displayName(alice)}`)","migrationContext":"Java: name.isEmpty() on null throws NullPointerException, so code writes name == null || name.isEmpty() (or StringUtils.isEmpty). Kotlin: name.isNullOrEmpty(). Python: 'if not name' treats None and \"\" alike. C#: string.IsNullOrEmpty(name). EK9: an unset String gives an unset answer to 'is empty', and an if skips an unset condition - so write 'not name? or name is empty' when missing and blank mean the same thing.","keywords":["?","String","blank","empty","is empty","is not empty","isNullOrEmpty","missing","null or empty","set check","tri-state","unset"],"primaryTopics":["strings","tri-state","unset"],"typicalErrors":[],"companions":[]}
{"id":1500,"category":"Streams and Pipelines","question":"What happens when a function in a stream pipeline throws an exception?","url":"https://ek9.io/qa/QA1500.html","alternatePhrasings":["Does a stream pipeline keep going after one item throws?","How do I catch an exception thrown inside a map or filter stage?","What happens to async tasks when one of them throws?","Can I skip a bad item in a pipeline instead of stopping it?"],"answer":"An exception that a stage's code does not catch itself breaks the WHOLE pipeline. No further item is pulled, no later stage runs, nothing more reaches the terminal, and the exception flies to the nearest enclosing try - in the function that runs the pipeline, or in whatever called it. If nothing catches it, the program ends with the Exception's own exit code (see Q1432).\n\nThis holds for every stage that runs your code: filter, map, call, flatten, sort by a comparator, group by, uniq by, tee into your own sink, your own source iterator, collect into your own type, and '>' into your own StringOutput. In the program below the first pipeline parses \"12\", throws on \"oops\", and the catch prints 'pipeline stopped'; \"30\" and \"45\" are never parsed.\n\nTO SKIP A BAD ITEM, CATCH INSIDE THE STAGE\nPut the try in the function the stage calls. parsePriceOrZero catches the failure, gives 0 for that item, and the second pipeline carries on to the end.\n\nASYNC\nWhen an async task throws while others run, the FIRST failure observed is the one that propagates, when the pipeline next takes a result. The other tasks are cancelled and waited for first, so none of them is still running when your catch runs.\n\nRESOURCES\nA pipeline inside a try with resources behaves like any other statement there: the exception goes to the catch, then finally runs, then the resources are closed in reverse declaration order.\n\nSee Q134 for try/catch. See Q1436 for catching versus letting the program exit.","ek9Example":"#!ek9\ndefines module qa.streams.pipelineexceptions\n\n  defines function\n\n    parsePrice()\n      -> text as String\n      <- rtn as Integer: Integer(text)\n      stdout <- Stdout()\n      stdout.println(`parsing ${text}`)\n      if not rtn?\n        throw Exception(`Not a price: ${text}`, 3)\n\n    parsePriceOrZero()\n      -> text as String\n      <- rtn as Integer: 0\n      try\n        rtn: parsePrice(text)\n      catch\n        -> ex as Exception\n        stdout <- Stdout()\n        stdout.println(`skipped: ${ex.reason()}`)\n\n  defines program\n\n    PipelineExceptionDemo()\n      stdout <- Stdout()\n      prices <- [\"12\", \"oops\", \"30\", \"45\"]\n\n      try\n        total <- cat prices | map with parsePrice | collect as List of Integer\n        stdout.println(`unreachable: ${total}`)\n      catch\n        -> ex as Exception\n        stdout.println(`pipeline stopped: ${ex.reason()}`)\n\n      recovered <- cat prices | map with parsePriceOrZero | collect as List of Integer\n      stdout.println(`recovered: ${recovered}`)","migrationContext":"Java streams: an exception from a lambda also stops the stream and propagates, but a checked exception cannot leave a lambda at all. Python generators: an exception from the generator stops iteration. EK9: every stage behaves the same way, including async (first failure wins, the other tasks are stopped first), and the exception is an ordinary EK9 Exception you catch around the pipeline or inside the stage.","keywords":["async","catch","exception","filter","map","pipeline","propagate","stage","stream","try"],"primaryTopics":["exception in a pipeline stage","catch inside a stage function"],"typicalErrors":[],"companions":[]}
{"id":1520,"category":"Error Handling and Exceptions","question":"When does EK9 close a closeable value, and where may I keep one?","url":"https://ek9.io/qa/QA1520.html","alternatePhrasings":["Is 'cat TextFile(path).input() > lines' closed after it reads the file?","Why must a named closeable be declared in a try?","Who owns a closeable value I never assign to a variable?","How do I keep a file or a connection open across several statements?","Can I return a closeable from a function or keep it in a property?"],"answer":"A closeable value is one whose type has an 'operator close' (a TextFile's input() or output(), a TCP or UDP connection, a Server, a Transaction, your own class). EK9 decides who closes it from WHERE it lives, so a leak cannot be written:\n\nA closeable value you never NAME is owned by the evaluation that created it - usually its statement - and is closed as soon as that evaluation finishes - normally, when a stream stage such as 'head' stops early, or when an exception leaves it (an operand that runs only sometimes, such as the right of 'and', closes its own: see Q1940):\n\n  lines <- List() of String\n  cat TextFile(\"data.txt\").input() > lines\n\nThe input is closed as soon as that cat has finished, so this can run thousands of times without running out of file handles. Two unnamed closeables in one statement are closed last-made first.\n\nA closeable value you NAME must be declared as a try-with-resources resource. It stays open for the whole try (and its catch and finally) and is closed when the try ends:\n\n  try\n    -> input <- TextFile(\"data.txt\").input()\n    while input.hasNext()\n      stdout.println(input.next())\n\nHolding one in a plain local variable is a compile error, because nothing would ever close it:\n\n  input <- TextFile(\"data.txt\").input()   // ERROR E81056\n  stdout.println(input.next())\n\nTwo places may hold a closeable without a try. RETURNING one hands it to the caller, who then names it in a try (or leaves it unnamed):\n\n  openLog()\n    <- rtn as CloseableStringInput: TextFile(\"log.txt\").input()\n\nand a PROPERTY may hold one - the object owns it, and its type's close ('default operator close', or the generated part of an 'operator close' you write) closes it, then calls the super class's close. Closing is generated, never written: there is no 'close' statement (E01094) and the close operator is never called as a method (E81060), so only the owner's generated code ever closes a value (see Q1600). A standard stream (Stdin, Stdout, Stderr) is borrowed, not owned, and is never closed; so are the input and output of a TCP connection, which belong to the connection.\n\nThe program below shows each case with a closeable whose close prints.","ek9Example":"#!ek9\ndefines module qa.errorhandling.closeableownership\n\n  defines class\n\n    Tracker\n      name as String?\n\n      default private Tracker()\n\n      Tracker()\n        -> label as String\n        name: label\n\n      describe() as pure\n        <- rtn as String: `tracker ${name}`\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${name}`)\n\n      default operator ?\n\n    Owner\n      tracker as Tracker?\n\n      default private Owner()\n\n      Owner()\n        -> label as String\n        tracker: Tracker(label)\n\n      describe() as pure\n        <- rtn as String: tracker.describe()\n\n      default operator close\n\n      default operator ?\n\n  defines function\n\n    makeTracker()\n      -> label as String\n      <- rtn as Tracker: Tracker(label)\n\n  defines program\n\n    CloseableOwnershipDemo()\n      stdout <- Stdout()\n\n      stdout.println(Tracker(\"unnamed\").describe())\n\n      try\n        -> named <- Tracker(\"named\")\n        stdout.println(named.describe())\n        stdout.println(\"still open inside the try\")\n\n      try\n        -> made <- makeTracker(\"made\")\n        stdout.println(made.describe())\n\n      try\n        -> owner <- Owner(\"owned\")\n        stdout.println(owner.describe())","migrationContext":"Java: try-with-resources is optional - an AutoCloseable in a plain local, or created inline as new FileReader(path).read(), compiles and leaks; only SpotBugs or an IDE inspection notices. Python: 'with open(...)' is optional too; open(path).read() relies on reference counting to close. C#: 'using' is optional; CA2000 is a warning. Rust: Drop closes everything at scope end. EK9: an unnamed closeable is closed by its statement, a named one must be a try-with-resources resource (E81056) - there is no third place for a leak to live.","keywords":["E81056","TextFile","cat","close","closeable","file handle","input","leak","operator close","ownership","resource","try","try-with-resources"],"primaryTopics":["resource management","try-with-resources","closeable"],"typicalErrors":[{"error":"E81056","correct":"      try\n        -> named <- Tracker(\"named\")\n        stdout.println(named.describe())\n        stdout.println(\"still open inside the try\")","incorrect":"      named <- Tracker(\"named\")\n      stdout.println(named.describe())\n      stdout.println(\"still open inside the try\")","explanation":"A closeable value held in a named local variable must be declared as a try-with-resources resource, or nothing would ever close it. See ek9 -h E81056 for details."}],"companions":[]}
{"id":1540,"category":"Testing","question":"How do I test that my program exits with a particular exit code?","url":"https://ek9.io/qa/QA1540.html","alternatePhrasings":["What does @Test: \"EXIT:3\" do in EK9?","My @Test program returns 3 and the test fails - how do I say that 3 is expected?","How do I declare the expected exit code of an EK9 test?","Can a test check a non-zero exit status in EK9?"],"answer":"Declare it on the @Test directive with \"EXIT:<n>\", where n is a whole number from 0 to 255.\n\nA program's returned Integer is its exit code, and under 'ek9 -t' the test judges it: a test passes only when its program exits with exactly the code the test expects. Without an EXIT part that is 0, so a test program that returns 3 FAILS with 'Test program exited with code 3'. Declare the 3 and the same program passes:\n\n  @Test: \"EXIT:3\"\n  EmptyOrderIsRejected()\n    <- rtn as Integer: exitCodeFor(noOrder)\n    assert rtn?\n\nThe declared code is checked both ways: a test declaring \"EXIT:3\" whose program exits 0 or 4 also fails ('Test program exited with code 0 but its @Test declares \"EXIT:3\"').\n\nIT MUST BE THE PROGRAM'S OWN CODE\nReturn the code from the program. An uncaught Exception or Panic also ends the program with a non-zero code, but it writes to stderr as it does, and the test runner reports that as an error, never as the expected exit.\n\nCOMBINING WITH A REQUIREMENT AND A GROUP\nA @Test carries up to three parts, in any order: a requirement id, an execution group and the exit code.\n  @Test: \"REQ:input-validation\": \"validation\": \"EXIT:3\"\nEach part is told apart by what it contains, so a mistake is a compile error rather than a silently ignored expectation: \"EXIT:abc\", \"EXIT:\", \"EXIT:256\", a second EXIT part, and a misspelled prefix such as \"EXT:3\" (it contains a colon, so it cannot quietly become a group name) are all rejected with E50200.\n\nSee Q1437 for how a program's exit code differs from the ek9 command's. See Q806 for test groups. See Q155 for writing tests.","ek9Example":"#!ek9\ndefines module qa.testdeep.expectedexitcode\n\n  defines function\n\n    exitCodeFor() as pure\n      -> order as String\n      <- rtn as Integer: 0\n      if order is empty\n        rtn: 3\n\n  defines constant\n    noOrder <- \"\"\n\n  defines program\n\n    //Returns 3 for an empty order, and declares that 3 is what it must exit with.\n    @Test: \"EXIT:3\"\n    EmptyOrderIsRejected()\n      <- rtn as Integer: exitCodeFor(noOrder)\n      assert rtn?","migrationContext":"Java: JUnit has no declared exit status; testing System.exit needs a SecurityManager workaround or a subprocess and a hand-written assertion. Python: pytest.raises(SystemExit) and then checking .code. Go: run the binary with os/exec and inspect ExitCode(). EK9: the expected exit code is part of the @Test directive and is checked when the source compiles.","keywords":["@Test","EXIT","exit code","exit status","expected exit code","non-zero exit","returned Integer","test"],"primaryTopics":["test expected exit code","@Test EXIT","testing exit status"],"typicalErrors":[{"error":"E50200","correct":"    @Test: \"EXIT:3\"","incorrect":"    @Test: \"EXT:3\"","explanation":"The expected exit code is written EXIT:<n>. A misspelled prefix contains a colon, so it is rejected rather than quietly becoming an execution group and dropping the expectation. See ek9 -h E50200 for details."}],"companions":[]}
{"id":1600,"category":"Error Handling and Exceptions","question":"Who closes a closeable value, and can I close one myself?","url":"https://ek9.io/qa/QA1600.html","alternatePhrasings":["Can a function close a resource it was passed as a parameter?","Why is 'close conn' error E01094?","Can I write 'close conn' for a connection held in a property?","Who owns a closeable value in EK9?","Can a dynamic function close a variable it captured?"],"answer":"No - you never close anything yourself. Every closeable value has exactly ONE owner, decided by where it lives, and the owner's close is generated by the compiler:\n- a LOCAL is a try-with-resources resource, and the try closes it when it ends;\n- a value that is never NAMED - a call result, a constructed value - is closed by the statement that made it;\n- a PROPERTY is owned by its object: the type's close - 'default operator close', or an 'operator close' you write for extra work - closes it after its body, last acquired first, then calls its super class's close;\n- a PARAMETER belongs to the caller, which lends it for the call;\n- a CAPTURE of a named value belongs to the scope that made the dynamic function or class;\n- the RETURNING variable is handed to the caller, who becomes its owner;\n- 'this' is owned by whoever made the object.\n\nSo there is no 'close' statement (E01094) and the close operator is never called as a method (E81060). A close written by a non-owner is a double close, or a close while the owner is still using the value - Java allows both, EK9 cannot express them. A type owns its properties simply by declaring its close:\n\n  default operator close\n\nA slot that will close its value - a closeable property or a try resource - therefore takes only a NEW value (E81062).\n\nIn the program below, Session owns its Connection and its generated close closes it; report() only uses the connection it is given and leaves the close to the try that owns it.","ek9Example":"#!ek9\ndefines module qa.errorhandling.onlytheownercloses\n\n  defines class\n\n    Connection\n      name as String?\n\n      default private Connection()\n\n      Connection()\n        -> label as String\n        name: label\n\n      describe() as pure\n        <- rtn as String: `connection ${name}`\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${name}`)\n\n      default operator ?\n\n    Session\n      connection as Connection?\n\n      default private Session()\n\n      Session()\n        -> label as String\n        connection: Connection(label)\n\n      describe() as pure\n        <- rtn as String: connection.describe()\n\n      default operator close\n\n      default operator ?\n\n  defines function\n\n    report()\n      -> connection as Connection\n      stdout <- Stdout()\n      stdout.println(connection.describe())\n\n  defines program\n\n    CloseOwnershipDemo()\n      stdout <- Stdout()\n\n      try\n        -> session <- Session(\"owned\")\n        stdout.println(session.describe())\n\n      try\n        -> connection <- Connection(\"borrowed\")\n        report(connection)","migrationContext":"Java: any code holding an AutoCloseable may call close() on it - a method that was only passed a stream often closes it, and the caller's try-with-resources closes it again; a class may close a field from any method. Rust: a borrower (&T) cannot drop what it borrowed; only the owner drops it. C#: CA2000/CA2213 are warnings. EK9: only the owner closes, and every close is generated - there is no close statement (E01094) and close() is never called as a method (E81060).","keywords":["E01094","E81060","capture","close","double close","operator close","owner","ownership","parameter","property"],"primaryTopics":["resource management","closeable","ownership"],"typicalErrors":[{"error":"E01094","correct":"      stdout.println(connection.describe())","incorrect":"      stdout.println(connection.describe())\n      close connection","explanation":"'close' is not a statement: a parameter belongs to the caller, whose try closes it, so a close here would close it twice. Every close is generated by the owner. See ek9 -h E01094 for details."},{"error":"E81060","correct":"      stdout.println(connection.describe())","incorrect":"      stdout.println(connection.describe())\n      connection.close()","explanation":"The close operator is never called as a method: the caller's try closes the connection it lent, so this call would close it twice. See ek9 -h E81060 for details."}],"companions":[]}
{"id":1620,"category":"Error Handling and Exceptions","question":"Can a method return a closeable it already holds, or must it return a new one?","url":"https://ek9.io/qa/QA1620.html","alternatePhrasings":["Why is a getter for my connection property error E81057?","How do I lend a resource I own without the caller closing it?","Can a function return its parameter when the type has operator close?","Why can't my operator + on a closeable class return this?"],"answer":"Returning a closeable value hands it to the caller, and the caller closes it - as a try-with-resources resource, or at the end of the statement when it is never named. So a method, operator, function or dynamic function whose declared return type is closeable must return a NEW instance (E81057 otherwise). That is a constructor call, or a call of another callable that itself declares a closeable return type, because that callable is held to the same rule:\n\n  openLog()\n    <- rtn as CloseableStringInput: TextFile(\"log.txt\").input()\n\nAnything somebody else already owns is an error: 'this', one of its own properties or a view of one (the object's generated close closes those), a parameter (the caller owns it), a captured variable, a try-with-resources resource (the try closes it as it ends, so the caller would get a closed object), or a call whose result may be held elsewhere. Every way the returning variable gets a value is checked - its initialiser, each assignment (an 'if rtn := ... then rtn?' head included) and each arm of a switch used as its value:\n\n  passBack()\n    -> input as CloseableStringInput\n    <- rtn as CloseableStringInput: input   // ERROR E81057\n\nA resource you hold is never handed out - not by returning it, and not wrapped in a 'view' type of your own, because a type that keeps the resource in a property owns it and would close it too (E81062, E81067). To let other code USE it, LEND it: the LOAN pattern (QA1740) keeps the resource in its owner and gives the owner a method that takes a function and calls it with the resource, so the callee borrows it for that call only. The other two borrower forms - a view the standard library provides, such as a TCP connection's input() and output(), and a dynamic class inside the owning try that captures the resource by name - are in Q1900. Never acquire with a fluent open() that returns 'this' (QA1720). The program below has a factory, a factory that delegates to it, and an owner that hands out a fresh closeable while keeping its own.","ek9Example":"#!ek9\ndefines module qa.errorhandling.closeablereturns\n\n  defines class\n\n    Tracker\n      name as String?\n\n      default private Tracker()\n\n      Tracker()\n        -> label as String\n        name: label\n\n      describe() as pure\n        <- rtn as String: `tracker ${name}`\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${name}`)\n\n      default operator ?\n\n    Owner\n      tracker as Tracker?\n\n      default private Owner()\n\n      Owner()\n        -> label as String\n        tracker: Tracker(label)\n\n      spare()\n        <- rtn as Tracker: Tracker(\"spare\")\n\n      describe() as pure\n        <- rtn as String: tracker.describe()\n\n      default operator close\n\n      default operator ?\n\n  defines function\n\n    makeTracker()\n      -> label as String\n      <- rtn as Tracker: Tracker(label)\n\n    delegatingFactory()\n      -> label as String\n      <- rtn as Tracker: makeTracker(label)\n\n  defines program\n\n    CloseableReturnDemo()\n      stdout <- Stdout()\n\n      try\n        -> made <- delegatingFactory(\"made\")\n        stdout.println(made.describe())\n\n      try\n        -> owner <- Owner(\"owned\")\n        stdout.println(owner.describe())\n        try\n          -> spare <- owner.spare()\n          stdout.println(spare.describe())","migrationContext":"Java: a getter may return a field that is an AutoCloseable, and nothing says who closes it - the caller's try-with-resources then closes the object's own connection under it. C#: the same with IDisposable; CA2000/CA2213 are advisory. Rust: returning a borrowed &T cannot be dropped by the caller, and returning an owned value moves it out. EK9: a declared closeable return must be a new instance (E81057); lend a held resource as an argument instead (the loan pattern).","keywords":["E81057","close","closeable","factory","getter","lend","loan","operator close","ownership","return"],"primaryTopics":["resource management","closeable","ownership"],"typicalErrors":[{"error":"E81057","correct":"      spare()\n        <- rtn as Tracker: Tracker(\"spare\")","incorrect":"      spare()\n        <- rtn as Tracker: tracker","explanation":"spare() declares a closeable return type, so it must return a NEW instance; returning the property 'tracker' would let the caller close the object Owner still owns and closes itself. See ek9 -h E81057 for details."}],"companions":[]}
{"id":1640,"category":"Error Handling and Exceptions","question":"Can I keep a closeable in a variable of a trait or Any type?","url":"https://ek9.io/qa/QA1640.html","alternatePhrasings":["Why is 'input as StringInput: TextFile(path).input()' an error?","Can I store an HTTP server in a variable declared as IO?","Can I pass a closeable to a parameter whose type is not closeable?","Why must a variable holding a closeable keep the closeable type?","What does E81059 mean?"],"answer":"No. Closeability cannot be erased by a store. A closeable value (its type has an 'operator close') may only be STORED - declared, assigned, or kept in a property or the returning variable - where the DECLARED type is closeable too. Storing one where the declared type has no close - a trait such as IO or StringInput, or Any - is error E81059:\n\n  input as StringInput: TextFile(\"data.txt\").input()   // ERROR E81059\n  stdout.println(input.next())\n\nThrough a StringInput nobody can call close, the statement that made the value sees it stored and does not close it, and the named-local rule (E81056) does not apply because StringInput is not closeable - so it would leak.\n\nKeep the closeable type, and name it as a try-with-resources resource:\n\n  try\n    -> input <- TextFile(\"data.txt\").input()\n    stdout.println(input.next())\n\nAn ARGUMENT is not a store. Passing a closeable to a parameter of a narrower, non-closeable type lends the callee a view; the owner - the statement for an unnamed value, the try for a named one - still closes it:\n\n  show(TextFile(\"data.txt\").input())\n\nA slot declared as the Closeable trait keeps its close, so it is allowed. A standard stream (Stdin, Stdout, Stderr) is borrowed, never closed, so 'input as StringInput: Stdin()' is fine.\n\nThe program below lends a closeable to a function that only knows its trait, then keeps one in a try; its close prints, so you can see each one closed.","ek9Example":"#!ek9\ndefines module qa.errorhandling.closeableerasure\n\n  defines trait\n\n    Greeter\n      greeting() as pure\n        <- rtn as String?\n\n  defines class\n\n    Line with trait of Greeter\n      text as String?\n\n      default private Line()\n\n      Line()\n        -> initialText as String\n        text: initialText\n\n      override greeting() as pure\n        <- rtn as String: `hello ${text}`\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${text}`)\n\n      default operator ?\n\n  defines function\n\n    show()\n      -> greeter as Greeter\n      stdout <- Stdout()\n      stdout.println(greeter.greeting())\n\n  defines program\n\n    CloseableErasureDemo()\n      stdout <- Stdout()\n\n      show(Line(\"argument\"))\n\n      try\n        -> line <- Line(\"kept\")\n        stdout.println(line.greeting())","migrationContext":"Java: an AutoCloseable can be assigned to any supertype (Object, Readable, an interface without close) and then never closed; nothing in the language notices. C#: an IDisposable stored as object or an interface without Dispose is the same leak. Rust: ownership travels with the value whatever trait object it is viewed through, so Drop still runs. EK9: the declared type of the place a closeable is stored must keep its close (E81059); a narrower view is only for an argument, while the owner keeps it.","keywords":["CloseableStringInput","E81059","IO","StringInput","argument","close","closeable","declared type","leak","operator close","store","trait","try-with-resources"],"primaryTopics":["resource management","try-with-resources","closeable"],"typicalErrors":[{"error":"E81059","correct":"      try\n        -> line <- Line(\"kept\")\n        stdout.println(line.greeting())","incorrect":"      greeter as Greeter: Line(\"kept\")\n      stdout.println(greeter.greeting())","explanation":"A closeable value may only be stored where the declared type is closeable: through a Greeter nothing could ever close it. Keep the closeable type in a try-with-resources resource. See ek9 -h E81059 for details."}],"companions":[]}
{"id":1660,"category":"Error Handling and Exceptions","question":"Can I call close() on a resource like a method?","url":"https://ek9.io/qa/QA1660.html","alternatePhrasings":["Why is conn.close() an error E81060?","How do I close a file or connection early in EK9?","Can a handler call close() on a server it captured?","How does a sub type close the resources its super holds?"],"answer":"No. The close operator is never called as a method - 'res.close()', 'this.close()', a bare 'close()' inside the type, 'make().close()' and 'super.close()' are all E81060, on any receiver. A closeable value is closed by its OWNER, exactly once:\n- a try-with-resources resource is closed by its try, when the try ends;\n- a closeable value that is never named is closed by the statement that made it;\n- a property is closed by its type's GENERATED close - 'default operator close', or after the body of an 'operator close' you write - which closes the type's own closeable properties and then calls its super class's close:\n\n  default operator close\n\nThe method form would get round that rule: a try resource closed inside its try is closed again when the try ends, a parameter or capture is closed while its owner still uses it, and a property closed in an ordinary method is closed again by its type's operator close.\n\nTo stop early, end the work with a condition (a loop flag) instead of closing the value under its owner. 'super.close()' is no exception: the generated close already calls the super class's close, so calling it by hand would close the super's part twice.\n\nIn the program below, the try closes the reader once, the loop stops on a flag, and Journal's generated close closes its own property and then calls its super's.","ek9Example":"#!ek9\ndefines module qa.errorhandling.closeisnotamethod\n\n  defines class\n\n    Reader as open\n      name as String?\n\n      default private Reader()\n\n      Reader()\n        -> label as String\n        name: label\n\n      read() as pure\n        <- rtn as String: `read ${name}`\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${name}`)\n\n      default operator ?\n\n    Journal is Reader\n      held as Reader?\n\n      Journal()\n        -> label as String\n        super(label)\n        held: Reader(label + \"-held\")\n\n      default operator close\n\n      default operator ?\n\n  defines program\n\n    CloseIsNotAMethod()\n      stdout <- Stdout()\n\n      try\n        -> reader <- Reader(\"first\")\n        reading <- true\n        while reading\n          stdout.println(reader.read())\n          reading: false\n\n      try\n        -> journal <- Journal(\"journal\")\n        require journal?","migrationContext":"Java: close() is an ordinary method - any code holding the reference may call it, any number of times, and keep using the object afterwards; try-with-resources then closes it again. Python: f.close() inside a 'with' block, followed by the block's own close. EK9: one way to close, used only by the owner - the try, the statement, or the type's generated close (E81060 for the method form, super.close() included).","keywords":["E81060","close","close()","double close","operator close","super.close","try-with-resources","use after close"],"primaryTopics":["resource management","closeable","ownership"],"typicalErrors":[{"error":"E81060","correct":"          stdout.println(reader.read())\n          reading: false","incorrect":"          stdout.println(reader.read())\n          reader.close()","explanation":"The try owns the reader and closes it when the try ends, so calling close() here would close it twice. End the loop with a condition instead. See ek9 -h E81060 for details."}],"companions":[]}
{"id":1700,"category":"Error Handling and Exceptions","question":"How do I shut a server down from a handler when only its owner may close it?","url":"https://ek9.io/qa/QA1700.html","alternatePhrasings":["How do I stop a TCP server from inside its connection handler?","How do I end HTTP serve() from a signal handler or admin endpoint?","What is the difference between stop() and close on a server?","Why can a handler not close the server it captured?","How does accept() return so the program can exit?"],"answer":"Call the server's stop(). A server that serves in a loop - TCP's accept(handler) and acceptUntilComplete(handler, readyHandler), HTTP's serve() and serveUntilComplete(readyHandler) - runs until stop() is called, from a connection handler, a signal handler or a control connection, on any thread. stop() ends serving WITHOUT closing: accept() or serve() returns true, and the server stays set, still open and still owned, until the try that made it ends and closes it.\n\nA handler cannot close the server instead. The handler captured the server, and a capture belongs to the scope that made it - the try, whose generated close closes it. There is no 'close' statement (E01094), and 'server.close()' is E81060: closing it in the handler would close it twice (once in the handler, once when the try ends) and would close it while accept() was still using it. Only the owner closes; everyone else stops.\n\nThe program below makes the server a try-with-resources resource. Its handler reads one request and, on 'shutdown', calls server.stop(). A client connects before accept() is called - a TCP server listens from construction, so the connection waits in the backlog - and accept() serves it and then returns. The program prints that the server is still set after accept() returns, and the end of the try closes it.\n\nFor HTTP the shape is the same: make the HTTP a try resource, call httpServer.serve(), and call httpServer.stop() from a signal handler (Signals().register) or an admin operation; serve() returns once in-flight requests have drained, and the try closes the server. A stopped server cannot serve again - make a new one.\n\nacceptUntilComplete and serveUntilComplete stop the server for you when their ready handler (a TCPReadyHandler / HTTPReadyHandler, or a pure TCPReadyObserver / HTTPReadyObserver) ends - normally, by Exception or by Panic - and they too only stop it: the try still closes it. See Q1760.","ek9Example":"#!ek9\ndefines module qa.errorhandling.stopaserver\n\n  defines function\n\n    sendShutdown()\n      -> port as Integer\n      loopback <- Networking().loopbackV4()\n      try\n        -> connection <- TCP(NetworkProperties(loopback, port)).connect()\n        if connection?\n          connection.output().println(\"shutdown\")\n\n  defines program\n\n    StopFromAHandler()\n      stdout <- Stdout()\n      port <- 28207\n\n      try\n        -> server <- TCP(NetworkProperties(port: port, backlog: 2, maxConcurrent: 2, localOnly: true))\n        stopOnRequest <- (server, stdout) is TCPHandler as function\n          request <- input.next()\n          stdout.println(`handler received ${request}`)\n          if request == \"shutdown\"\n            server.stop()\n\n        sendShutdown(port)\n        served <- server.accept(stopOnRequest)\n        stdout.println(`accept returned ${served}, server still set ${server?}`)\n\n      stdout.println(\"the try has closed the server\")","migrationContext":"Java: ServerSocket.close() from another thread both ends accept() and releases the socket, so any code holding the server can shut it down - and the try-with-resources that owns it closes it again. Jetty/Undertow separate stop() from destroy(). Go: http.Server.Shutdown(ctx) stops serving and Close() releases. Rust (tokio): a shutdown signal ends the accept loop; the listener is dropped by its owner. EK9: stop() ends serving and only the owner closes - 'server.close()' is E81060.","keywords":["E81060","HTTP","TCP","accept","close","handler","owner","serve","server","shutdown","stop"],"primaryTopics":["resource management","server shutdown","ownership"],"typicalErrors":[{"error":"E81060","correct":"          if request == \"shutdown\"\n            server.stop()","incorrect":"          if request == \"shutdown\"\n            server.close()","explanation":"The handler captured the server, and a capture belongs to the try that made it, which closes it. Closing it in the handler would close it twice, and while accept() was still serving. Call server.stop() - it ends accept() without closing. See ek9 -h E81060 for details."}],"companions":[]}
{"id":1720,"category":"Error Handling and Exceptions","question":"Why can't a closeable have an open() method that returns this - how do I acquire a resource?","url":"https://ek9.io/qa/QA1720.html","alternatePhrasings":["How do I write the Resource().open() pattern in EK9?","Why is a fluent open() on my closeable class error E81057?","Should a resource be acquired in its constructor or in a separate open() call?","What replaces 'file = File(path); file.open()' in EK9?"],"answer":"A type that has an 'operator close' acquires its resource in its CONSTRUCTOR, and a try-with-resources header (or the statement that creates it) owns it from the moment it exists. There is no separate 'open' step, so there is nothing to forget and no half-open object. The two-step 'construct, then fluent open()' idiom is rejected because 'open() <- rtn as Res: this' hands back the object that the receiver expression already created: both the try and the temporary then own the same object, which is closed twice (E81057 - a closeable return must be a NEW instance).\n\nName it in a try when you need it across several statements:\n\n  try\n    -> named <- Tracker(\"named\")\n    stdout.println(named.describe())\n\nLeave it unnamed for a one-shot read - the statement closes it:\n\n  lines <- List() of String\n  cat TextFile(\"data.txt\").input() > lines\n\nThe program below acquires in the constructor, uses one tracker named in a try and one unnamed, and has a method that hands back a NEW tracker, which the caller's try then closes.","ek9Example":"#!ek9\ndefines module qa.errorhandling.acquireinconstructor\n\n  defines class\n\n    Tracker\n      name as String?\n\n      default private Tracker()\n\n      Tracker()\n        -> label as String\n        stdout <- Stdout()\n        name: label\n        stdout.println(`opened ${name}`)\n\n      describe() as pure\n        <- rtn as String: `tracker ${name}`\n\n      again()\n        <- rtn as Tracker: Tracker(name)\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${name}`)\n\n      default operator ?\n\n  defines program\n\n    AcquireInConstructorDemo()\n      stdout <- Stdout()\n\n      try\n        -> named <- Tracker(\"named\")\n        stdout.println(named.describe())\n\n      stdout.println(Tracker(\"unnamed\").describe())\n\n      try\n        -> second <- Tracker(\"first\").again()\n        stdout.println(second.describe())","migrationContext":"Java: new Resource().open() or a builder returning this leaves the construct-then-open window where an exception leaks the object. Python: __init__ then .open() has the same window; 'with' is optional. C++: RAII - acquire in the constructor, release in the destructor - is exactly this model. Rust: a constructor such as File::open returns the owned value. EK9: acquire in the constructor; a method returning a closeable must return a new instance (E81057).","keywords":["E81057","RAII","acquire","close","closeable","constructor","fluent","open","ownership","resource","try"],"primaryTopics":["resource management","closeable","constructor"],"typicalErrors":[{"error":"E81057","correct":"      again()\n        <- rtn as Tracker: Tracker(name)","incorrect":"      again()\n        <- rtn as Tracker: this","explanation":"A method with a closeable return type must return a NEW instance; returning 'this' would give the caller the object that the receiver expression already owns, so it would be closed twice. Acquire in the constructor instead of a fluent open(). See ek9 -h E81057 for details."}],"companions":[]}
{"id":1740,"category":"Error Handling and Exceptions","question":"How do I let other code use a resource my object owns without letting it close or keep it?","url":"https://ek9.io/qa/QA1740.html","alternatePhrasings":["What is the loan pattern for a closeable property?","How do I lend a file I hold when a StringInput view is not available?","Why does returning my CloseableStringInput property give E81057 or E81059?","How do I let a caller read the lines of a file my class keeps open?"],"answer":"When an object owns a closeable in a property, other code must not receive it as a return value (E81057 - a closeable return must be a new instance) or store it in a trait or Any slot (E81059). Reverse the direction: LEND it. Give the owner a method that takes a function, and have the owner call that function with the resource it holds. The resource is passed as an ARGUMENT, so it is borrowed for that call only; the owner's generated close still closes it.\n\nDeclare the function type with a parameter whose type is NOT closeable (a trait without 'operator close', or StringInput for a file's lines), so the callee can read but never close. Then the owner:\n\n  lend()\n    -> user as PageUser\n    <- rtn as Integer: user(notebook)\n\nThis is the pattern to use for a file held as a CloseableStringInput property, because (unlike a TCP connection's input() and output(), which return a separate StringInput view) a file's closeable has no view to return. The loan is one of three ways to use a resource you do not own; the other two - a view the standard library provides, and a by-name capture inside the owning try - are in Q1900. The program below owns a closeable Notebook, lends it to a function that counts lines, and lets the try close it afterwards.","ek9Example":"#!ek9\ndefines module qa.errorhandling.loanpattern\n\n  defines trait\n\n    Pages\n      next() as abstract\n        <- rtn as String?\n\n      hasNext() as abstract\n        <- rtn as Boolean?\n\n  defines class\n\n    Notebook with trait of Pages\n      lines as List of String?\n      position as Integer: 0\n\n      default private Notebook()\n\n      Notebook()\n        -> first as String\n        lines: List() of String\n        lines += first\n        lines += \"second\"\n        lines += \"third\"\n\n      override next()\n        <- rtn as String: lines.getOrDefault(position, \"\")\n        position++\n\n      override hasNext()\n        <- rtn as Boolean: position < length lines\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(\"closed notebook\")\n\n      default operator ?\n\n    Owner\n      notebook as Notebook?\n\n      default private Owner()\n\n      Owner()\n        -> first as String\n        notebook: Notebook(first)\n\n      lend()\n        -> user as PageUser\n        <- rtn as Integer: user(notebook)\n\n      default operator close\n\n      default operator ?\n\n  defines function\n\n    PageUser() as abstract\n      -> pages as Pages\n      <- rtn as Integer?\n\n    countPages() is PageUser\n      -> pages as Pages\n      <- rtn as Integer: 0\n      while pages.hasNext()\n        if pages.next()?\n          rtn++\n\n  defines program\n\n    LoanPatternDemo()\n      stdout <- Stdout()\n\n      try\n        -> owner <- Owner(\"first\")\n        stdout.println(`pages: ${owner.lend(countPages)}`)\n        stdout.println(\"owner still holds the notebook\")","migrationContext":"Java: a getter returns the held Reader and any caller can close it, or keep it past the owner's lifetime. Rust: a closure or function taking &Reader borrows it for the call only. Swift: a non-escaping closure parameter has the same lifetime limit. Python: a context manager that yields the handle lets the body close it. EK9: the owner calls a supplied function with the resource - an argument is a loan.","keywords":["E81057","E81059","StringInput","TextFile","borrow","close","closeable","function parameter","lend","loan","ownership","property"],"primaryTopics":["resource management","closeable","ownership"],"typicalErrors":[],"companions":[]}
{"id":1760,"category":"Web Services","question":"How do I test a web service in one program, calling it from its own client?","url":"https://ek9.io/qa/QA1760.html","alternatePhrasings":["How do I run an HTTP service and a client against it in the same EK9 program?","What is HTTP serveUntilComplete for?","How do I know the HTTP server is listening before my client calls it?","How do I stop serve() once my test requests are done?","What is the difference between HTTPReadyHandler and HTTPReadyObserver?"],"answer":"Use serveUntilComplete(readyHandler) instead of serve(). It binds the application's services, starts listening and only THEN runs your ready handler, on its own thread, passing it the serving HTTP. The handler is the service's client: it can make requests and print what comes back, with no race against the server starting. Serving ends when the handler calls server.stop() or simply returns - and if it throws, the server is stopped and the Exception (or Panic) reaches the caller of serveUntilComplete, so a failed test never hangs. It returns true, like serve().\n\nThe server is STOPPED, never closed: it is a resource of the try that made it, and only that owner closes it, so the program below prints that it is still set after serveUntilComplete returns. Closing it in the handler is an error - server.close() is E81060, there is no close statement (E01094), and closing it is the owning try's job.\n\nThere are two ready types. An HTTPReadyHandler can act - make requests, change state, stop the server. An HTTPReadyObserver is pure - it can only observe that the server is up (log a readiness line, say); serving ends when it returns. TCP has the same pair for acceptUntilComplete(handler, readyHandler): TCPReadyHandler and TCPReadyObserver.\n\nBind to port 0 so no fixed port can collide: HTTP writes the port the operating system chose back into the NetworkProperties it was given, and the ready handler captures those properties to find it. The client below speaks HTTP/1.1 over a TCP socket - a request line, headers, a blank line - and reads the status line and the last line, the body. A program may create only one HTTP (E12023), so test a second scenario in a second program.","ek9Example":"#!ek9\ndefines module qa.webservices.testinprocess\n\n  defines service\n\n    GreetingService for :/greeting open\n\n      greet() as GET for :/{name}\n        -> personName as String :=: PATH \"name\"\n        <- response as HTTPResponse: (who: personName) with trait of HTTPResponse\n          override status() as pure\n            <- rtn as Integer: 200\n          override content()\n            <- rtn as Bytes: Bytes().withUtf8(`hello ${who}`)\n          override contentType() as pure\n            <- rtn as String: \"text/plain\"\n          default operator ?\n\n  defines function\n\n    httpGet()\n      ->\n        port as Integer\n        path as String\n      <- rtn as String: \"no reply\"\n      loopback <- Networking().loopbackV4()\n      try\n        -> connection <- TCP(NetworkProperties(loopback, port)).connect()\n        if connection?\n          connection.output().println(`GET ${path} HTTP/1.1`)\n          connection.output().println(\"Host: localhost\")\n          connection.output().println(\"Connection: close\")\n          connection.output().println(\"\")\n          statusLine <- connection.input().next()\n          body <- String()\n          while connection.input()?\n            body := connection.input().next()\n          rtn: `${statusLine} | ${body}`\n\n  defines application\n\n    GreetingApp\n      register GreetingService()\n\n  defines program\n\n    TestServiceInProcess() with application of GreetingApp\n      stdout <- Stdout()\n      properties <- NetworkProperties(0, true)\n\n      try\n        -> httpServer <- HTTP(properties)\n        client <- (stdout, properties) is HTTPReadyHandler as function\n          stdout.println(`client got: ${httpGet(properties.port, \"/greeting/Steve\")}`)\n          server.stop()\n\n        served <- httpServer.serveUntilComplete(client)\n        stdout.println(`serveUntilComplete returned ${served}, server still set ${httpServer?}`)\n\n      stdout.println(\"the try has closed the server\")","migrationContext":"Java: Spring's @SpringBootTest(webEnvironment = RANDOM_PORT) or an embedded Jetty/HttpServer started in a @BeforeEach with a latch or polling to know it is listening, then stopped in @AfterEach. Go: httptest.NewServer gives a running server and its URL. Python: a TestClient or a thread running the server. EK9: serveUntilComplete runs the client only once the server listens, and stops the server when the client ends, whatever way it ends.","keywords":["HTTP","HTTPReadyHandler","HTTPReadyObserver","client","in-process","port","serveUntilComplete","service","stop","test"],"primaryTopics":["testing a service","HTTP server lifecycle","ready handler"],"typicalErrors":[{"error":"E81060","correct":"          server.stop()","incorrect":"          server.close()","explanation":"The ready handler is lent the server; it does not own it, and the close operator is never called as a method. Call server.stop() - serving ends, and the try that made the server closes it. See ek9 -h E81060 for details."}],"companions":[]}
{"id":1780,"category":"Error Handling and Exceptions","question":"Can I put a closeable resource in a List, an Optional or my own generic type?","url":"https://ek9.io/qa/QA1780.html","alternatePhrasings":["Why is 'servers <- [TCP(properties)]' an error?","Can I keep several open connections in a List and close them later?","Why can't I write Optional of TCP or Dict of (String, HTTP)?","Can a generic type parameter be constrained by a closeable type?","What does E81061 mean?"],"answer":"No. A closeable type (one with an 'operator close') is never a type argument of a generic type or a generic function. List of TCP, Optional of Res, Dict of (String, HTTP), a Box of Res of your own and Acceptor of TCP are all error E81061, and so are the same parameterisations when they are INFERRED - [server], Optional(res), {\"key\": res}, or a generic function called with a closeable, which infers T as that type:\n\n  keepAll()\n    -> server as TCP\n    servers <- [server]   // ERROR E81061 - a List of TCP\n\nA generic's 'T constrain by Res', where Res is closeable, is refused at its declaration too: it could only ever be given a closeable.\n\nThe reason is ownership. A closeable value has exactly one owner, and the compiler must see that owner close it: a try-with-resources resource, a property closed by its type's generated close, or the caller of a callable that returns a new one. A generic type or function has no such owner, so what it held would never be closed - or would be closed while somebody else still owned it.\n\nA closeable can be a PARAMETER type: that is a loan, and the lender keeps ownership. So pick one of three shapes:\n\n1. Hold the resources as properties of a type you own, which declares 'default operator close' - its generated close closes them.\n2. Acquire each one as a try-with-resources resource where it is used.\n3. Lend it to the code that needs it, as a parameter, instead of collecting it.\n\nA callback that receives a resource is declared as a non-generic function type with that parameter - the way TCPReadyHandler takes a TCP - never as Acceptor of TCP.\n\nThe program below holds two resources as properties of an owning type, lends each one to a function, and keeps a third in a try; every close prints, so you can see each resource closed exactly once.","ek9Example":"#!ek9\ndefines module qa.errorhandling.closeablenevertypeargument\n\n  defines class\n\n    Line\n      text as String?\n\n      default private Line()\n\n      Line()\n        -> initialText as String\n        text: initialText\n\n      greeting() as pure\n        <- rtn as String: `hello ${text}`\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${text}`)\n\n      default operator ?\n\n    <?- Owns its resources as properties - not a List of Line - and its generated close closes them, last first -?>\n    Lines\n      first as Line?\n      second as Line?\n\n      default private Lines()\n\n      Lines()\n        ->\n          firstText as String\n          secondText as String\n        first: Line(firstText)\n        second: Line(secondText)\n\n      greetAll()\n        show(first)\n        show(second)\n\n      default operator close\n\n      default operator ?\n\n  defines function\n\n    <?- A closeable as a PARAMETER is a loan: the lender keeps ownership and closes it -?>\n    show()\n      -> line as Line\n      stdout <- Stdout()\n      stdout.println(line.greeting())\n\n  defines program\n\n    CloseableTypeArgumentDemo()\n\n      try\n        -> lines <- Lines(\"one\", \"two\")\n        lines.greetAll()\n\n      try\n        -> line <- Line(\"three\")\n        show(line)","migrationContext":"Java: a List<Socket> or Optional<Connection> compiles, and closing every element is left to a finally loop that is easy to get wrong or forget. C#: a List<IDisposable> is the same; 'using' only covers a single variable. Rust: a Vec<File> drops each element when the Vec is dropped, because ownership moves into the container. EK9: a container never owns a resource; ownership stays with a try, a property of a type you write, or the caller of a factory, and the compiler refuses the type argument (E81061).","keywords":["Dict","E81061","List","Optional","close","closeable","constrain by","generic","loan","operator close","ownership","try-with-resources","type argument"],"primaryTopics":["resource management","closeable","generics"],"typicalErrors":[{"error":"E81061","correct":"      try\n        -> line <- Line(\"three\")\n        show(line)","incorrect":"      try\n        -> line <- Line(\"three\")\n        lines <- [line]\n        show(line)","explanation":"A closeable type is never a type argument: [line] would be a List of Line, and a List has no owner that closes what it holds. Lend the resource as a parameter instead, or hold it as a property of a type you own. See ek9 -h E81061 for details."}],"companions":[]}
{"id":1840,"category":"Error Handling and Exceptions","question":"Can I give my object a resource I have already opened?","url":"https://ek9.io/qa/QA1840.html","alternatePhrasings":["Why is 'logger <- Logger(journal)' an error when journal is a try resource?","Why can't I write 'try -> mine <- param' to make sure a parameter is closed?","Can a try resource be another object's property?","What does E81062 mean?"],"answer":"Not if something else already owns it. Every closeable value has exactly ONE owner, and that owner closes it. A closeable property (closed by its type's operator close) and a try-with-resources resource (closed when the try ends) are slots that will close what they hold, so a closeable may enter one only when it is NEW - a constructor call such as Journal(\"audit\"), or a call whose declaration returns a new closeable, such as TextFile(path).input() or a function that returns a Journal it made. Anything else is error E81062: a parameter (the caller owns it), a try resource, your own property or another object's property already has an owner, and storing it again would close it twice - and close it while its first owner is still using it.\n\nA constructor that stores its own parameter in a closeable property takes ownership of it - a transfer - so every construction must hand that parameter a new value. Logger(Journal(\"audit\")) is right: the logger is the journal's only owner. Logger(journal), with journal a try resource, gives one journal two owners, the try and the logger, and is E81062 at the argument. The same holds when a constructor forwards its parameter to super(...), and a parameter may go into only one property.\n\nCode that only USES a resource someone else owns takes it as a parameter and leaves the closing to its owner - the loan pattern. The program below gives the logger a new journal and lends a second journal to a function; every close prints, so you can see each journal closed exactly once.","ek9Example":"#!ek9\ndefines module qa.errorhandling.closeableslotneedsanewvalue\n\n  defines class\n\n    Journal\n      name as String?\n\n      default private Journal()\n\n      Journal()\n        -> label as String\n        name: label\n\n      write()\n        -> text as String\n        stdout <- Stdout()\n        stdout.println(`${name}: ${text}`)\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${name}`)\n\n      default operator ?\n\n    <?- Keeps the journal it is given: a transfer, so it must be handed a NEW journal -?>\n    Logger\n      journal as Journal?\n\n      default private Logger()\n\n      Logger()\n        -> target as Journal\n        journal: target\n\n      log()\n        -> text as String\n        journal.write(text)\n\n      default operator close\n\n      default operator ?\n\n  defines function\n\n    <?- Only uses a journal someone else owns: a parameter is a loan -?>\n    note()\n      -> journal as Journal\n      journal.write(\"borrowed\")\n\n  defines program\n\n    FreshStoreDemo()\n\n      try\n        -> logger <- Logger(Journal(\"audit\"))\n        logger.log(\"started\")\n\n      try\n        -> journal <- Journal(\"notes\")\n        note(journal)","migrationContext":"Java: 'new BufferedWriter(writer)' takes the writer, and closing both the wrapper and the original in two try-with-resources blocks closes it twice; nothing stops you. C#: 'leaveOpen' flags and CA2000/CA2213 analyzers try to track who disposes what. Rust: moving a value into a struct transfers ownership, and using the moved value again is a compile error. EK9: a closeable may enter a property or a try resource only when it is new (E81062), so every closeable has one owner by construction.","keywords":["E81062","close","closeable","constructor","fresh","loan","new value","operator close","ownership","property","transfer","try-with-resources"],"primaryTopics":["resource management","closeable","ownership"],"typicalErrors":[{"error":"E81062","correct":"        -> logger <- Logger(Journal(\"audit\"))","incorrect":"        ->\n          audit <- Journal(\"audit\")\n          logger <- Logger(audit)","explanation":"Logger keeps the journal it is given in a closeable property, so it must be handed a NEW one. A named try resource already has an owner, the try, and both would close it. See ek9 -h E81062 for details."}],"companions":[]}
{"id":1841,"category":"Error Handling and Exceptions","question":"How do I reconnect or replace a resource my object holds?","url":"https://ek9.io/qa/QA1841.html","alternatePhrasings":["Why is 'conn := Connection(..)' in a reconnect method an error?","Can I use ':=?' to open a connection only if it is not open?","How do I retry with a new connection in EK9?","What does E81063 mean?"],"answer":"You do not re-point it: a closeable property is set ONCE, in its own initialiser or its own type's constructor, while it holds nothing yet, and its type's operator close releases it when the object is closed. Any other assignment - ':=' in a reconnect() method, an assignment through another object, a constructor setting it a second time - is error E81063, because it drops the resource the property held and nothing would ever close it. The guarded ':=?' is no escape: a connection that failed or was closed is present but UNSET, so ':=?' replaces it and still drops it.\n\nWhen code needs a new resource for each attempt, acquire it in a try where it is used - a fresh connection per try. Each attempt's connection is closed when its try ends, whether the attempt worked or not. When an object needs a different resource for its whole life, make a new object: its constructor acquires the resource and its operator close releases it.\n\nThe program below retries a send three times with a fresh connection per try; the first two attempts fail, and every connection is closed exactly once.","ek9Example":"#!ek9\ndefines module qa.errorhandling.replaceaheldresource\n\n  defines class\n\n    Connection\n      name as String?\n      healthy as Boolean?\n\n      default private Connection()\n\n      Connection()\n        ->\n          text as String\n          works as Boolean\n        name: text\n        healthy: works\n\n      label() as pure\n        <- rtn as String: name\n\n      send()\n        -> message as String\n        <- rtn as Boolean: healthy\n        stdout <- Stdout()\n        if healthy\n          stdout.println(`sent ${message} on ${name}`)\n        else\n          stdout.println(`${name} failed`)\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${name}`)\n\n      default operator ?\n\n    <?- Its connection is set once, in the constructor, and closed with the session -?>\n    Session\n      conn as Connection?\n\n      default private Session()\n\n      Session()\n        -> text as String\n        conn: Connection(text, true)\n\n      describe()\n        stdout <- Stdout()\n        stdout.println(`session on ${conn.label()}`)\n\n      default operator close\n\n      default operator ?\n\n  defines program\n\n    RetryWithFreshConnection()\n      stdout <- Stdout()\n\n      try\n        -> session <- Session(\"primary\")\n        session.describe()\n\n      maximumAttempts <- 3\n      sent <- false\n      attempt <- 1\n\n      while not sent and attempt <= maximumAttempts\n        try\n          -> conn <- Connection(`attempt ${attempt}`, attempt == maximumAttempts)\n          sent := conn.send(\"hello\")\n        attempt++\n\n      stdout.println(`sent: ${sent}`)","migrationContext":"Java: 'this.conn = connect()' in a reconnect method silently leaks the old socket unless you remember to close it first. C#: the same, and a disposable field re-assigned is a classic CA2213 miss. Python: 'self.conn = connect()' leaks until the garbage collector finalises it. EK9: re-pointing a closeable property is a compile error (E81063); acquire a fresh resource in a try per attempt.","keywords":["E81063","assign if unset","close","closeable","operator close","property","re-point","reconnect","retry","try-with-resources"],"primaryTopics":["resource management","closeable","ownership"],"typicalErrors":[{"error":"E81063","correct":"      describe()\n        stdout <- Stdout()\n        stdout.println(`session on ${conn.label()}`)","incorrect":"      describe()\n        conn := Connection(\"replacement\", true)\n        stdout <- Stdout()\n        stdout.println(`session on ${conn.label()}`)","explanation":"A closeable property is set once, while its object is constructed. Re-pointing it in a method drops the connection it held and nothing closes it; acquire a fresh connection in a try instead. See ek9 -h E81063 for details."}],"companions":[]}
{"id":1842,"category":"Error Handling and Exceptions","question":"In what order are the resources my object holds closed?","url":"https://ek9.io/qa/QA1842.html","alternatePhrasings":["Why must I acquire closeable properties in the order they are declared?","Why is setting a closeable property in my constructor an E81064 error?","How do I make sure a writer is closed before the ledger it writes to?","What does E81064 mean?"],"answer":"In REVERSE declaration order - the last declared is closed first. That is reverse ACQUISITION order (last made, first closed) only when the constructor acquires its closeable properties in declaration order, so the compiler requires exactly that: a constructor that sets a closeable property while one declared after it already holds a resource is error E81064. Property initialisers run in declaration order before the constructor body, so a closeable property with an initialiser counts as acquired before anything the body sets, and one path through the constructor acquiring out of order is enough.\n\nThe order matters as soon as one resource depends on another: a writer that writes to a ledger must be closed - and flushed - before the ledger. Declare the foundation first and what depends on it after, and acquire them in that order.\n\nThe program below declares a ledger, then a writer that writes to it, and acquires them in that order. Each prints when it is opened and when it is closed: the writer, made last, is closed first.","ek9Example":"#!ek9\ndefines module qa.errorhandling.closeablepropertiesindeclarationorder\n\n  defines class\n\n    Channel\n      name as String?\n\n      default private Channel()\n\n      Channel()\n        -> text as String\n        name: text\n        stdout <- Stdout()\n        stdout.println(`opened ${name}`)\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${name}`)\n\n      default operator ?\n\n    <?- Declared, and acquired, foundation first: the writer is closed first, the ledger last -?>\n    Report\n      ledger as Channel?\n      writer as Channel?\n\n      Report()\n        ledger: Channel(\"ledger\")\n        writer: Channel(\"writer\")\n\n      default operator close\n\n      default operator ?\n\n  defines program\n\n    ReportWriter()\n      stdout <- Stdout()\n\n      try\n        -> report <- Report()\n        stdout.println(\"writing the report\")","migrationContext":"C++: members are destroyed in reverse declaration order whatever the constructor's initialiser list says, and compilers only warn (-Wreorder) when the two disagree. Rust: fields are dropped in DECLARATION order, an acknowledged accident. Java and C#: you close fields by hand in whatever order you write. EK9: properties are closed in reverse declaration order and the constructor must acquire them in declaration order (E81064).","keywords":["E81064","acquisition order","close","closeable","constructor","declaration order","operator close","order","property"],"primaryTopics":["resource management","closeable","ownership"],"typicalErrors":[{"error":"E81064","correct":"    Report\n      ledger as Channel?\n      writer as Channel?","incorrect":"    Report\n      writer as Channel?\n      ledger as Channel?","explanation":"Properties are closed in reverse declaration order, so declared this way the ledger would be closed before the writer that writes to it. Declare them in the order the constructor acquires them. See ek9 -h E81064 for details."}],"companions":[]}
{"id":1843,"category":"Error Handling and Exceptions","question":"Can a type that holds a closeable resource use 'default operator :=:'?","url":"https://ek9.io/qa/QA1843.html","alternatePhrasings":["Why is 'default operator' an error on my class that holds a connection?","How do I copy an object that owns a resource?","Why can't the generated copy operator copy a closeable property?","What does E81065 mean?"],"answer":"No. The generated copy is shallow: it copies each property by reference. On a type that holds a closeable property, after 'first :=: second' both objects would hold second's resource, both would close it, and the resource first held would never be closed. No generated copy can be right for a resource, so 'default operator :=:' - written alone, or by the bare 'default operator', which includes it - is error E81065 on such a type.\n\nAsk only for the default operators the type needs ('default operator ?', 'default operator $', ...). If the type really must be copied, write 'operator :=:' yourself and copy only the values that are not resources: each object keeps, and later closes, the resource it acquired itself.\n\nThe program below copies one holder over another with a hand-written copy: the first takes the second's label but keeps its own resource, and each resource is closed exactly once.","ek9Example":"#!ek9\ndefines module qa.errorhandling.nodefaultcopyofaresourceholder\n\n  defines class\n\n    Resource\n      name as String?\n\n      default private Resource()\n\n      Resource()\n        -> text as String\n        name: text\n\n      label() as pure\n        <- rtn as String: name\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${name}`)\n\n      default operator ?\n\n    Holder\n      label as String?\n      held as Resource?\n\n      default private Holder()\n\n      Holder()\n        -> text as String\n        label: String(text)\n        held: Resource(`${text}-resource`)\n\n      describe() as pure\n        <- rtn as String: `${label} holding ${held.label()}`\n\n      <?- Copies only what is not a resource: each holder keeps the resource it acquired -?>\n      operator :=:\n        -> other as Holder\n        label :=: other.label\n\n      default operator close\n\n      default operator ?\n\n  defines program\n\n    CopyAResourceHolder()\n      stdout <- Stdout()\n\n      try\n        ->\n          first <- Holder(\"first\")\n          second <- Holder(\"second\")\n        first :=: second\n        stdout.println(second.describe())\n        stdout.println(first.describe())","migrationContext":"Java: clone() of an object with a Socket field shares the socket, and closing both clones closes it twice. C++: the implicitly generated copy constructor copies a raw handle the same way, which is why the rule of three/five exists. Rust: a type holding a File is not Copy and Clone must be written. EK9: the generated copy is refused on a type that holds a closeable (E81065); write ':=:' and copy only what is not a resource.","keywords":[":=:","E81065","close","closeable","copy","default operator","operator close","property","shallow copy"],"primaryTopics":["resource management","closeable","operators"],"typicalErrors":[{"error":"E81065","correct":"      operator :=:\n        -> other as Holder\n        label :=: other.label","incorrect":"      default operator :=:","explanation":"The generated copy would copy the closeable property by reference, so both holders would close one resource. Write ':=:' and copy only what is not a resource. See ek9 -h E81065 for details."}],"companions":[]}
{"id":1860,"category":"Error Handling and Exceptions","question":"Which of my types must declare their own operator close, and what may that close do?","url":"https://ek9.io/qa/QA1860.html","alternatePhrasings":["Why must my sub-class declare operator close when its super already has one?","Why can't a trait's operator close have a body?","Why can't the operator close of an open class call one of its own methods?","What do E81066, E81067 and E81068 mean?"],"answer":"If you hold it, you close it. Three rules decide where a close lives and what it may do.\n\n1. A class, record, component or dynamic class with a closeable PROPERTY must DECLARE its own 'operator close' (E81067). An inherited close does not count: it was written before the sub-type added its property and cannot see it, so the property would never be closed. A sub-type that adds a closeable property declares its own close - usually 'default operator close'. Closing is generated: after the close's body (none, for the default) it closes the type's own closeable properties, last acquired first, and then calls its super class's close:\n\n  Journal is Ledger\n    notes as Page?\n    ...\n    default operator close\n\nA sub-type that adds no closeable property relies on the inherited close. Injected properties ('x as T!') are closed by the application that owns them, and a 'by'-delegation proxy close closes its delegate.\n\n2. A trait's 'operator close' is ABSTRACT - it has no body (E81066). A trait holds no properties, so it has nothing of its own to close; the class with the trait writes the close.\n\n3. The 'operator close' of an OPEN or ABSTRACT type can never call a method a subtype may override, nor hand out 'this' (E81068). A subtype's generated close closes the subtype's own properties before its super's close runs, so an overridden method called from the super's close would work on resources that are already closed. Call a PRIVATE helper, or the methods of the properties the type itself holds. This is the same rule E08061 applies to the constructor of an open type, at the other end of the object's life.\n\nThe program below has an open Ledger that holds one Page, tidies up in its own close and lets the generated close close the Page, a Journal that adds a second Page and declares its own close, and a Bookmark whose trait declares an abstract close. Every close prints, so you can see each Page closed exactly once.","ek9Example":"#!ek9\ndefines module qa.errorhandling.declareyourownclose\n\n  defines trait\n\n    <?- A trait's operator close has no body: it is abstract -?>\n    Markable\n      operator close as pure\n\n  defines class\n\n    Page\n      label as String?\n\n      default private Page()\n\n      Page()\n        -> initialLabel as String\n        label: initialLabel\n\n      write()\n        stdout <- Stdout()\n        stdout.println(`writing in ${label}`)\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${label}`)\n\n      default operator ?\n\n    <?- Open: its close calls only a private helper and the methods of the property it holds -?>\n    Ledger as open\n      title as String?\n      page as Page?\n\n      default private Ledger()\n\n      Ledger()\n        -> initialTitle as String\n        title: initialTitle\n        page: Page(initialTitle)\n\n      describe() as pure\n        <- rtn as String: `ledger ${title}`\n\n      write()\n        page.write()\n\n      private tidy() as pure\n        stdout <- Stdout()\n        stdout.println(`tidy ${title}`)\n\n      operator close as pure\n        tidy()\n\n      default operator ?\n\n    <?- Adds a closeable property, so it declares its own close and chains to the super's -?>\n    Journal is Ledger\n      notes as Page?\n\n      Journal()\n        -> title as String\n        super(title)\n        notes: Page(`${title} notes`)\n\n      default operator close\n\n      default operator ?\n\n    <?- The class with the trait writes the close -?>\n    Bookmark with trait of Markable\n      page as Page?\n\n      Bookmark()\n        page: Page(\"bookmark\")\n\n      mark()\n        page.write()\n\n      default operator close\n\n      default operator ?\n\n  defines program\n\n    DeclareYourOwnClose()\n\n      try\n        -> journal <- Journal(\"journal\")\n        journal.write()\n\n      try\n        -> bookmark <- Bookmark()\n        bookmark.mark()","migrationContext":"Java: a subclass that adds a field and forgets to override close() (or forgets super.close()) leaks silently; a close() that calls an overridable method is a known trap that no compiler reports. C#: analyzers CA2213 and CA2215 look for the forgotten member and base call, and CA2215 ships off by default. C++: a destructor body runs with virtual calls bound to its own class, so a base destructor cannot reach a derived member. EK9: a type with a closeable property must declare its own close (E81067), a trait's close is abstract (E81066), and an open type's close cannot dispatch to an override (E81068).","keywords":["E81066","E81067","E81068","abstract","close","closeable","open","operator close","override","property","super.close","trait"],"primaryTopics":["resource management","closeable","inheritance"],"typicalErrors":[{"error":"E81067","correct":"      Journal()\n        -> title as String\n        super(title)\n        notes: Page(`${title} notes`)\n\n      default operator close\n","incorrect":"      Journal()\n        -> title as String\n        super(title)\n        notes: Page(`${title} notes`)\n","explanation":"Journal adds a closeable property, so it must declare its own operator close: the inherited Ledger close cannot see 'notes', which would never be closed. 'default operator close' closes what the type holds and then calls the super's close. See ek9 -h E81067 for details."},{"error":"E81066","correct":"    Markable\n      operator close as pure\n","incorrect":"    Markable\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(\"trait close\")\n","explanation":"A trait's operator close is abstract: a trait holds no properties, so it has nothing to close. The class with the trait writes the close. See ek9 -h E81066 for details."},{"error":"E81068","correct":"      operator close as pure\n        tidy()","incorrect":"      operator close as pure\n        describe()","explanation":"Ledger is open, and describe() is a method a subtype may override. A subtype's close closes its own properties before Ledger's close runs, so the override would see closed resources. Call a private helper such as tidy() instead. See ek9 -h E81068 for details."}],"companions":[]}
{"id":1880,"category":"Error Handling and Exceptions","question":"How do I release the resources my class holds - and what does 'default operator close' close for me?","url":"https://ek9.io/qa/QA1880.html","alternatePhrasings":["Do I write 'close' for each closeable property in my operator close?","In what order does EK9 close the properties of my class?","Does my sub-class have to call super.close()?","Can a dynamic class or a dynamic function own a resource it captures?","What do E81069 and E81070 mean?"],"answer":"You declare a close, and EK9 generates the closing. 'default operator close' closes every closeable property your type holds - each one only if it is present, LAST acquired FIRST - and then calls your super class's close. A close you write yourself is for extra work only: its body runs first, with every property still open, and the same generated closing follows it as its finally, so it runs even when the body throws. You never close a property by hand - there is no 'close' statement (E01094) - and never call 'super.close()' (E81060): both would close something twice.\n\n  Notebook as open\n    cover as Page: Page(\"cover\")\n    index as Page: Page(\"index\")\n    default operator close        // closes index, then cover\n\nEvery close runs even when one fails: the first failure is rethrown after all of them, and later ones are attached to it. A constructor that fails part way closes the properties it had acquired before it rethrows.\n\nTwo rules keep closing honest. 'default operator close' with nothing to close - no closeable property and no super class close - is E81070: it would only make the type closeable and force its callers into a 'try'. And a captured NEW closeable belongs to whatever captured it: a dynamic CLASS can own it (it declares 'default operator close', which closes it), but a dynamic FUNCTION has no close, so capturing a new closeable there is E81069. A capture of something that is not new - a try resource captured by name - is a borrow, and its owner closes it.\n\nIn the program below, Diary's written close runs first with its page open, then 'today' is closed, then Notebook's close closes 'index' and 'cover'; a dynamic class owns and closes the page it captured; and a dynamic function borrows a try resource the try closes.","ek9Example":"#!ek9\ndefines module qa.errorhandling.releasewhatyouhold\n\n  defines trait\n\n    Readable\n      text()\n        <- rtn as String?\n\n  defines function\n\n    reading() as abstract\n      <- rtn as String?\n\n  defines class\n\n    Page\n      label as String?\n\n      default private Page()\n\n      Page()\n        -> initialLabel as String\n        label: initialLabel\n\n      read() as pure\n        <- rtn as String: `reading ${label}`\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${label}`)\n\n      default operator ?\n\n    <?- Holds no closeable: it needs no close at all -?>\n    Tag\n      name as String?\n\n      default private Tag()\n\n      Tag()\n        -> initialName as String\n        name: initialName\n\n      describe() as pure\n        <- rtn as String: `tag ${name}`\n\n      default operator ?\n\n    <?- Holds two pages and asks for the generated close: last acquired, first closed -?>\n    Notebook as open\n      cover as Page: Page(\"cover\")\n      index as Page: Page(\"index\")\n\n      default operator close\n\n      default operator ?\n\n    <?- A written close does extra work first; its own page and Notebook's close follow it -?>\n    Diary is Notebook\n      today as Page: Page(\"today\")\n\n      override operator close as pure\n        stdout <- Stdout()\n        stdout.println(`diary still open: ${today.read()}`)\n\n      default operator ?\n\n  defines program\n\n    ReleaseWhatYouHold()\n      stdout <- Stdout()\n\n      try\n        -> diary <- Diary()\n        stdout.println(\"writing the diary\")\n\n      try\n        -> reader <- (page: Page(\"captured\")) with trait of Readable as class\n          override text()\n            <- rtn as String: page.read()\n          default operator close\n          default operator ?\n        stdout.println(reader.text())\n\n      try\n        -> loose <- Page(\"loose\")\n        fn <- (loose) is reading as function\n          rtn: loose.read()\n        stdout.println(fn())\n\n      stdout.println(Tag(\"tagged\").describe())","migrationContext":"Java: close() is written by hand - each field closed in turn, super.close() remembered, and a field added later forgotten; one throwing close skips the rest unless every call is wrapped. C++ and Swift: destructors release members (in reverse order) and the base automatically - EK9's generated close is the same model, with 'default operator close' to ask for it. C#: Dispose is hand-written; analysers CA2213/CA2215 warn about forgotten fields and base calls. EK9: the compiler writes the closing, so neither can be forgotten.","keywords":["E01094","E81060","E81069","E81070","capture","close","closeable","default operator close","dynamic class","operator close","property","super"],"primaryTopics":["resource management","closeable","ownership"],"typicalErrors":[{"error":"E01094","correct":"      override operator close as pure\n        stdout <- Stdout()\n        stdout.println(`diary still open: ${today.read()}`)\n","incorrect":"      override operator close as pure\n        stdout <- Stdout()\n        stdout.println(`diary still open: ${today.read()}`)\n        close today\n","explanation":"'close' is not a statement: the generated close already closes 'today' after the body, so closing it by hand would close it twice. A written close does extra work only. See ek9 -h E01094 for details."},{"error":"E81070","correct":"      describe() as pure\n        <- rtn as String: `tag ${name}`\n\n      default operator ?","incorrect":"      describe() as pure\n        <- rtn as String: `tag ${name}`\n\n      default operator close\n\n      default operator ?","explanation":"Tag holds no closeable property and has no super class close, so a 'default operator close' would close nothing - it would only make Tag closeable and force its callers into a try. See ek9 -h E81070 for details."},{"error":"E81069","correct":"        fn <- (loose) is reading as function\n          rtn: loose.read()","incorrect":"        fn <- (fresh: Page(\"fresh\")) is reading as function\n          rtn: fresh.read()","explanation":"A dynamic function has no close, so it can never close a NEW closeable it captures. Capture a try resource by name (a borrow), or capture the new value in a dynamic class that declares 'default operator close'. See ek9 -h E81069 for details."}],"companions":[]}
{"id":1900,"category":"Error Handling and Exceptions","question":"How can my code use a closeable resource it does not own?","url":"https://ek9.io/qa/QA1900.html","alternatePhrasings":["How do I borrow a resource without closing it?","Can I write a view class that wraps a connection or file my object holds?","Why can't I return a view type that wraps my closeable property?","How does a dynamic class use a try resource without owning it?"],"answer":"BORROW it. A closeable has exactly one owner - the try that acquired it, the statement that made it, or the object that holds it in a property - and only the owner's generated close ever closes it. Code that merely uses the resource borrows it in one of three ways, and none of them can close it.\n\n1. AS AN ARGUMENT - the loan pattern. The owner keeps the resource and calls a function (or method) with it; the callee borrows it for that call only, and may not keep it (E81062) or close it (E81060):\n\n  lend()\n    -> reader as PageReader\n    <- rtn as String: reader(page)\n\n2. THROUGH A VIEW THE STANDARD LIBRARY PROVIDES. A TCP connection's input() and output() - and a TCP handler's 'input' and 'output' parameters - are a StringInput and a StringOutput, which have no 'operator close'. Hold or pass the view, never the connection: a registry of StringOutput views is how one connection's handler writes to all the others (Q1901). A view type of your OWN cannot be written: a class that keeps the resource in a property owns it, so it must close it (E81067) and must be given a NEW one (E81062) - it can never wrap a resource somebody else holds.\n\n3. INSIDE THE TRY THAT OWNS IT. A dynamic class made in the try that captures the resource BY NAME borrows it for as long as the try runs; the try still closes it, once. Capturing a NEW value instead - '(draft: Page(\"fresh\"))' - makes the dynamic class its owner, and an owner must declare its own close (E81067).\n\nThe program below lends a journal's page to a function, and lets a dynamic class read a try resource it captured by name. Each page is closed once, by its owner.","ek9Example":"#!ek9\ndefines module qa.errorhandling.borrowwithoutowning\n\n  defines trait\n\n    Reading\n      read() as pure\n        <- rtn as String?\n\n  defines class\n\n    Page\n      name as String?\n\n      default private Page()\n\n      Page()\n        -> label as String\n        name: label\n\n      read() as pure\n        <- rtn as String: `page ${name}`\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${name}`)\n\n      default operator ?\n\n    Journal\n      page as Page?\n\n      default private Journal()\n\n      Journal()\n        -> label as String\n        page: Page(label)\n\n      lend()\n        -> reader as PageReader\n        <- rtn as String: reader(page)\n\n      default operator close\n\n      default operator ?\n\n  defines function\n\n    PageReader() as abstract\n      -> page as Page\n      <- rtn as String?\n\n    firstWords() is PageReader\n      -> page as Page\n      <- rtn as String: `lent ${page.read()}`\n\n  defines program\n\n    BorrowWithoutOwning()\n      stdout <- Stdout()\n\n      try\n        -> journal <- Journal(\"journal\")\n        stdout.println(journal.lend(firstWords))\n\n      try\n        -> draft <- Page(\"draft\")\n        reader <- (draft) with trait of Reading as class\n          override read() as pure\n            <- rtn as String: `borrowed ${draft.read()}`\n          default operator ?\n        stdout.println(reader.read())","migrationContext":"Java: any code holding a reference to an AutoCloseable may close it or keep it, and a wrapper class decides by convention whether it 'owns' what it wraps. C#: the same with IDisposable and leaveOpen flags. Rust: a borrow (&T) cannot outlive or drop its owner. EK9: a borrower is an argument, a non-closeable view, or a by-name capture inside the owning try - and a type that keeps a closeable is always its owner.","keywords":["E81062","E81067","StringOutput","borrow","capture","closeable","dynamic class","loan","owner","view"],"primaryTopics":["resource management","closeable","ownership"],"typicalErrors":[{"error":"E81067","correct":"        reader <- (draft) with trait of Reading as class","incorrect":"        reader <- (draft: Page(\"fresh\")) with trait of Reading as class","explanation":"Capturing a NEW page makes the dynamic class its owner, and if you hold it, you close it: it would have to declare its own 'default operator close'. Capturing the try resource by name only borrows it. See ek9 -h E81067 for details."}],"companions":[]}
{"id":1901,"category":"Error Handling and Exceptions","question":"How do I broadcast a message to every open connection, like a chat server, when I cannot keep a list of connections?","url":"https://ek9.io/qa/QA1901.html","alternatePhrasings":["How do I keep a list of connected clients in EK9?","Why can't I hold a List of TCPConnection or a Dict of connections?","How do I write a chat server that sends to every client?","Can EK9 code keep a pool of connections?"],"answer":"Keep a registry of the connections' VIEWS, not of the connections. A closeable is never a type argument (E81061) - 'List of TCPConnection' and 'Dict of (String, TCPConnection)' are refused - because a collection is not an owner that closes what it holds: every connection already has an owner, the server, which closes it when its handler returns. A TCP handler is given each connection's 'input' and 'output' as a StringInput and a StringOutput, which have no 'operator close', so those may be kept, shared and written to by anyone:\n\n1. On entry, the handler REGISTERS its output view under the member's name.\n2. Anyone may BROADCAST: write a line to every registered view.\n3. On exit - in a finally, so it happens however the handler ends - the handler DEREGISTERS its view, before the server closes the connection behind it.\n\nThe handlers run concurrently, so the registry is shared state: every change goes through a MutexLock (E08251 otherwise), and a MutexLock is made only as a field initialiser (E08256). Here a Room holds 'MutexLock of Roster', and each of its methods enters the lock with a MutexKey function.\n\nThis is a recorded design decision, not a gap: EK9 code holds no collections of closeables. Code that must hold many resources at once - a connection pool - gets a Java-backed standard library type that LENDS a resource for one call (the loan pattern, Q1740) and closes them all itself; EK9 code never sees the collection.\n\nIn the program below the server's own client connects as alice and then bob. Each join and leave is broadcast to everyone still present, and each client prints what it hears.","ek9Example":"#!ek9\ndefines module qa.errorhandling.broadcasttoconnections\n\n  defines class\n\n    Roster\n      members as Dict of (String, StringOutput)?\n\n      Roster()\n        members: Dict() of (String, StringOutput)\n\n      add()\n        ->\n          name as String\n          output as StringOutput\n        members += DictEntry(name, output)\n\n      remove()\n        -> name as String\n        members -= name\n\n      tell()\n        -> text as String\n        for output in members.values()\n          output.println(text)\n\n      present() as pure\n        <- rtn as Integer: length members\n\n      default operator ?\n\n    Room\n      roster as MutexLock of Roster: MutexLock(Roster())\n\n      join()\n        ->\n          name as String\n          output as StringOutput\n        joining <- (name, output) is MutexKey of Roster as function\n          lockedItem.add(name, output)\n          lockedItem.tell(`${name} joined`)\n        require roster.enter(joining)\n\n      leave()\n        -> name as String\n        leaving <- (name) is MutexKey of Roster as function\n          lockedItem.remove(name)\n          lockedItem.tell(`${name} left, ${lockedItem.present()} present`)\n        require roster.enter(leaving)\n\n      default operator ?\n\n  defines function\n\n    connectTo()\n      -> port as Integer\n      <- rtn as TCPConnection: TCP(NetworkProperties(Networking().loopbackV4(), port)).connect()\n\n  defines program\n\n    BroadcastToConnections()\n      stdout <- Stdout()\n      port <- 28211\n      room <- Room()\n\n      member <- (room) is TCPHandler as function\n        name <- input.next()\n        room.join(name, output)\n        try\n          farewell <- input.next()\n          require farewell?\n        finally\n          room.leave(name)\n          output.println(`goodbye ${name}`)\n\n      clients <- (port, stdout) is TCPReadyHandler as function\n        try\n          -> alice <- connectTo(port)\n          alice.output().println(\"alice\")\n          stdout.println(`alice hears: ${alice.input().next()}`)\n          try\n            -> bob <- connectTo(port)\n            bob.output().println(\"bob\")\n            stdout.println(`bob hears: ${bob.input().next()}`)\n            stdout.println(`alice hears: ${alice.input().next()}`)\n            bob.output().println(\"bye\")\n            stdout.println(`bob hears: ${bob.input().next()}`)\n            stdout.println(`alice hears: ${alice.input().next()}`)\n          alice.output().println(\"bye\")\n          stdout.println(`alice hears: ${alice.input().next()}`)\n\n      try\n        -> server <- TCP(NetworkProperties(port: port, backlog: 2, maxConcurrent: 2, localOnly: true))\n        served <- server.acceptUntilComplete(member, clients)\n        stdout.println(`served ${served}`)","migrationContext":"Java: a chat server keeps a ConcurrentHashMap of Socket or PrintWriter and must remember to remove a client on every exit path, or it writes to a closed socket - or closes it twice when the map is cleaned up. Go: a map of net.Conn guarded by a mutex, with the same removal discipline. EK9: the registry holds non-closeable StringOutput views behind a MutexLock, the handler deregisters in a finally, and the server alone closes each connection.","keywords":["E81061","MutexLock","StringOutput","TCPHandler","broadcast","chat","connections","pool","registry","view"],"primaryTopics":["resource management","networking","ownership"],"typicalErrors":[{"error":"E81061","correct":"      members as Dict of (String, StringOutput)?","incorrect":"      members as Dict of (String, TCPConnection)?","explanation":"A TCPConnection is closeable, and a closeable is never a type argument: a Dict is not an owner that closes what it holds, and each connection's owner already closes it. Register the connection's StringOutput view instead. See ek9 -h E81061 for details."}],"companions":[]}
{"id":1902,"category":"Error Handling and Exceptions","question":"What happens when a close throws an exception - are the other resources still closed?","url":"https://ek9.io/qa/QA1902.html","alternatePhrasings":["If one operator close fails, does EK9 skip the rest?","Which exception do I catch when the try body and a close both fail?","What does a constructor that fails part way do with the resources it acquired?","Does a close exception replace the exception my code threw?","Where do the close failures go that were not rethrown?"],"answer":"Every close still runs. Wherever EK9 generates closing - the end of a try, the end of a statement that made an unnamed closeable, and the chain after a type's 'operator close' - each close runs in its own protected region, last acquired first:\n\n- if closes throw, the FIRST Exception is rethrown after all of them have run, and each later one is attached to it as SUPPRESSED - read them with 'ex.suppressed()', a List of Exception (QA1920), and an uncaught failure lists them as 'suppressed:' lines;\n- if the try body (or the acquisition) already threw, THAT exception is the one that propagates, and every close failure is suppressed onto it - a close never replaces the real failure;\n- a Panic is never caught there: a Panic in a close stops the remaining closes, and a Panic in the body still closes everything and then propagates unchanged.\n\nA constructor that fails PART WAY runs the same chain over what it had acquired: every closeable property that is present is closed, last first, and then the exception is rethrown - so a half-built object never leaks the resources it already holds.\n\nA close failure happens AFTER the try's own body and catch, so to catch it put the try-with-resources inside another try:\n\n  try\n    try\n      -> idle <- Machine()\n      stdout.println(`using ${idle?}`)\n  catch\n    -> ex as Exception\n    stdout.println(`caught: ${ex.reason()}`)\n    for failure in ex.suppressed()\n      stdout.println(`suppressed: ${failure.reason()}`)\n\nIn the program below, Machine has three parts and two of their closes throw: all three are closed, the first failure ('bad third', closed first) is the one caught and 'bad second' is in its suppressed() list. When the work itself throws, the work's exception is caught instead, with the Machine's close failure suppressed onto it. And an Assembly whose constructor fails after its frame was acquired closes the frame before the exception arrives.","ek9Example":"#!ek9\ndefines module qa.errorhandling.whenaclosefails\n\n  defines class\n\n    Part\n      name as String?\n\n      default private Part()\n\n      Part()\n        -> label as String\n        name: label\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closing ${name}`)\n        if name.startsWith(\"bad\")\n          throw Exception(`close of ${name} failed`)\n\n      default operator ?\n\n    Machine\n      first as Part: Part(\"first\")\n      second as Part: Part(\"bad second\")\n      third as Part: Part(\"bad third\")\n\n      default operator close\n\n      default operator ?\n\n    Assembly\n      frame as Part: Part(\"frame\")\n      motor as Part?\n\n      default private Assembly()\n\n      Assembly()\n        -> fitMotor as Boolean\n        if not fitMotor\n          throw Exception(\"no motor to fit\")\n        motor: Part(\"motor\")\n\n      default operator close\n\n      default operator ?\n\n  defines program\n\n    WhenACloseFails()\n      stdout <- Stdout()\n\n      try\n        try\n          -> idle <- Machine()\n          stdout.println(`using ${idle?}`)\n      catch\n        -> ex as Exception\n        stdout.println(`caught: ${ex.reason()}`)\n        for failure in ex.suppressed()\n          stdout.println(`suppressed: ${failure.reason()}`)\n\n      try\n        try\n          -> busy <- Machine()\n          require busy?\n          throw Exception(\"the work failed\")\n      catch\n        -> ex as Exception\n        stdout.println(`caught: ${ex.reason()}`)\n        for failure in ex.suppressed()\n          stdout.println(`suppressed: ${failure.reason()}`)\n\n      try\n        try\n          -> assembly <- Assembly(false)\n          stdout.println(`assembled ${assembly?}`)\n      catch\n        -> ex as Exception\n        stdout.println(`caught: ${ex.reason()}`)","migrationContext":"Java: try-with-resources closes every resource and adds later close failures with addSuppressed, but a hand-written close() that closes several fields stops at the first one that throws, and a constructor that throws leaks what it opened. C#: a throwing Dispose in a using block replaces the original exception. C++: a destructor that throws during unwinding terminates the program. EK9: the same rule everywhere closing is generated - every close runs, the first failure wins, later ones are suppressed, and a failed constructor closes what it acquired.","keywords":["Panic","close","constructor","default operator close","exception","operator close","partial construction","suppressed","try with resources"],"primaryTopics":["resource management","exceptions","closeable"],"typicalErrors":[],"companions":[]}
{"id":1920,"category":"Error Handling and Exceptions","question":"How do I see the close failures that were suppressed onto the exception I caught?","url":"https://ek9.io/qa/QA1920.html","alternatePhrasings":["How do I get the suppressed exceptions in EK9?","What is the EK9 equivalent of Java's getSuppressed()?","Several closes failed - where did the other exceptions go?","How do I log every close failure, not just the first one?","Why does a suppressed exception have suppressed exceptions of its own?"],"answer":"Call 'suppressed()' on the Exception you caught. It returns a 'List of Exception': the close failures that came AFTER this one and were attached to it rather than replacing it, in the order the closes ran.\n\n  catch\n    -> ex as Exception\n    stdout.println(`caught: ${ex.reason()}`)\n    for failure in ex.suppressed()\n      stdout.println(`  suppressed: ${failure.reason()}`)\n\nWherever EK9 generates closing - the end of a try, the end of a statement that made an unnamed closeable, and the chain after a type's 'operator close' - every close runs, the first Exception is the one that propagates and every later close failure is suppressed onto it. When the try body threw, the body's exception is the one you catch and the close failures are in its suppressed() list.\n\nA suppressed failure is an Exception like any other, so it can carry suppressed failures of its own. A type's generated close chain fails as ONE close: its first failure, with the rest of that chain suppressed onto it. So when a try body throws and then the resource's chain fails, you catch the body's exception, its suppressed() holds the chain's first failure, and THAT failure's suppressed() holds the rest - walk it recursively, as 'describe' does below, to see everything.\n\nThe List is always set (a collection is set when it is created) and is empty when nothing was suppressed, so you never need a guard before iterating it, and 'length ex.suppressed()' is 0 for an ordinary exception. Every sub-type of Exception inherits suppressed(). A Panic is never caught by the close regions, so it can never be a suppressed failure.\n\nIn the program below the Ledger closes its three parts last first and two of those closes throw: the first failure ('index') is caught with 'journal' suppressed onto it. When the posting itself fails, the posting's exception is caught, the ledger's 'index' failure is suppressed onto it, and 'journal' is suppressed onto 'index'. An exception with nothing suppressed answers an empty List.","ek9Example":"#!ek9\ndefines module qa.errorhandling.inspectsuppressed\n\n  defines class\n\n    Part\n      name as String?\n\n      default private Part()\n\n      Part()\n        -> label as String\n        name: label\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closing ${name}`)\n        if name <> \"totals\"\n          throw Exception(`close of ${name} failed`)\n\n      default operator ?\n\n    Ledger\n      journal as Part: Part(\"journal\")\n      index as Part: Part(\"index\")\n      totals as Part: Part(\"totals\")\n\n      default operator close\n\n      default operator ?\n\n  defines function\n\n    report()\n      -> ex as Exception\n      Stdout().println(`caught: ${ex.reason()}`)\n      for failure in ex.suppressed()\n        describe(failure, \"  \")\n\n    describe()\n      ->\n        failure as Exception\n        indent as String\n      Stdout().println(`${indent}suppressed: ${failure.reason()}`)\n      for inner in failure.suppressed()\n        describe(inner, indent + \"  \")\n\n  defines program\n\n    InspectSuppressed()\n      stdout <- Stdout()\n\n      try\n        try\n          -> ledger <- Ledger()\n          stdout.println(\"using the ledger\")\n      catch\n        -> ex as Exception\n        report(ex)\n\n      try\n        try\n          -> ledger <- Ledger()\n          require ledger?\n          throw Exception(\"the posting failed\")\n      catch\n        -> ex as Exception\n        report(ex)\n\n      try\n        throw Exception(\"nothing to close\")\n      catch\n        -> ex as Exception\n        report(ex)\n        stdout.println(`nothing suppressed: ${length ex.suppressed() == 0}`)","migrationContext":"Java: Throwable.getSuppressed() returns an array of what try-with-resources attached with addSuppressed. Python: the 'during handling of the above exception' chain (__context__). C#: no equivalent - a throwing Dispose replaces the original exception. EK9: ex.suppressed() returns a List of Exception, filled by every generated close (try resources, statement-owned temporaries and the property chain), never null and empty when nothing was suppressed.","keywords":["List of Exception","close","exception","getSuppressed","operator close","suppressed","suppressed()","try with resources"],"primaryTopics":["exceptions","resource management","closeable"],"typicalErrors":[{"error":"E50060","correct":"      for failure in ex.suppressed()","incorrect":"      for failure in ex.getSuppressed()","explanation":"EK9's Exception has no Java-style 'getSuppressed()'; the method is 'suppressed()' and it returns a List of Exception. See ek9 -h Exception for the full API."}],"companions":[]}
{"id":1940,"category":"Error Handling and Exceptions","question":"When exactly is a closeable I never name closed - what if it is made inside an 'and', a ternary or a coalescing operator?","url":"https://ek9.io/qa/QA1940.html","alternatePhrasings":["Is a closeable made on the right of 'and' closed if the left side is false?","When is a closeable made in a ternary arm closed?","Does a while condition close the closeable it makes on every iteration?","Is an unnamed closeable closed at the end of the statement or sooner?"],"answer":"An unnamed closeable is closed as soon as the evaluation that made it finishes - EK9 releases resources as soon as it can. Usually that evaluation is the whole statement, but an operand that runs only SOMETIMES is an evaluation of its own: the right of 'and' and 'or', a ternary arm, the right of '??', '?:', '<?' and '>?', the right of ':=?', a later 'else if' condition, a condition after a guard and a 'case' value. A closeable made there is closed when that operand finishes, and only if it ran:\n\n  both <- enabled and Tracker(\"checked\").ready()   // closed before 'both' is set, only when enabled\n\nA loop condition is evaluated again on every iteration, so it closes what it made every time:\n\n  while count < limit and Tracker(`loop ${count}`).ready()\n\nWhen the closeable IS the conditional's result - a ternary arm, or either side of a coalescing operator - it cannot be closed when the arm finishes, because the value is still to be used. The evaluation that USES the result owns it instead, and closes it when it finishes, only if that arm actually made it:\n\n  stdout.println(`chosen ${enabled <- Tracker(\"first\") else Tracker(\"second\")}`)   // closes the one that was made\n\nA named try resource picked by the same conditional is never closed by it - its try closes it.","ek9Example":"#!ek9\ndefines module qa.errorhandling.closedassoonasfinished\n\n  defines class\n\n    Tracker\n      name as String?\n\n      default private Tracker()\n\n      Tracker()\n        -> label as String\n        name: label\n\n      ready() as pure\n        <- rtn as Boolean: true\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(`closed ${name}`)\n\n      operator $ as pure\n        <- rtn as String: `tracker ${name}`\n\n      default operator ?\n\n  defines function\n\n    report()\n      -> enabled as Boolean\n      stdout <- Stdout()\n\n      both <- enabled and Tracker(\"checked\").ready()\n      stdout.println(`both ${both}`)\n\n      stdout.println(`chosen ${enabled <- Tracker(\"first\") else Tracker(\"second\")}`)\n\n      count <- 0\n      limit <- 2\n      while count < limit and Tracker(`loop ${count}`).ready()\n        stdout.println(`loop body ${count}`)\n        count++\n\n  defines program\n\n    ClosedAsSoonAsFinished()\n      report(true)\n      report(false)","migrationContext":"Java: a temporary AutoCloseable in an expression is never closed unless it is a try-with-resources resource, and a resource made in only one branch needs a nullable variable and a manual finally. C#: the same, with 'using' declarations at block scope. C++: a temporary is destroyed at the end of the full-expression that made it, whichever branch ran. EK9 closes it earlier still - when the evaluation that made it finishes - and only the ones that were made.","keywords":["close","closeable","coalescing","condition","resource management","short-circuit","temporary","ternary","while"],"primaryTopics":["resource management","closeable","ownership"],"typicalErrors":[],"companions":[]}
{"id":1960,"category":"Control Flow","question":"Can I give an Optional a new value inside the 'if o?' block that checked it?","url":"https://ek9.io/qa/QA1960.html","alternatePhrasings":["Why is 'if o := other() then o?' inside 'if o?' error E08040?","Why can't I re-point a checked Optional inside its safe block?","Is 'o := other()' allowed inside a safe access block?","Why does a while loop's own 'o :=? next()' head not count as reassignment?","How do I try a replacement Optional inside a block where the first one was checked?"],"answer":"No. Inside the scope that proved an Optional, Result or Iterator safe to unwrap ('if o?', or the body of a guard such as 'while o :=? next()'), EVERY store to it is E08040 - an assignment statement, and a nested construct's pre-flow alike:\n\n  o <- Optional(\"Steve\")\n  if o?\n    o := Optional(\"Other\")                    // E08040 - the statement re-points o\n    if o := Optional(\"Other\") then o?        // E08040 - so does a nested head\n      stdout.println(o.get())\n\nThe safe scope promises that o.get() works. After the store, o holds the NEW value, which nothing checked: an empty one would throw 'No such element' at the next o.get().\n\nWHAT TO DO INSTEAD\n- Guard into a NEW variable: 'if replacement <- other()' - the replacement has its own proof.\n- Re-point after the safe scope has ended: 'if name := nextName() then name?' below the 'if name?' block.\n\nA CONSTRUCT'S OWN HEAD IS THE PROOF\n'while current :=? nextName() then count < limit' fills current once, before the loop (a pre-flow always runs exactly once), and that guard is what makes current.get() safe in the loop. A while, for, switch or try marks its whole scope safe, head included, so the compiler judges the head against the scope AROUND the construct: it is an error only when an enclosing 'if current?' had already proven current safe.\n\nOnly a guard head ('<-', ':=?') proves anything by itself: 'if o := next() then ready' assigns blindly, so o.get() in its body is E08030 (Q1963) - write the check into the condition: 'then o? and ready'.\n\nSee Q84 for Optional's operations and Q86 for Result guards.","ek9Example":"#!ek9\ndefines module qa.flow.guard.safescope\n\n  defines function\n\n    firstName()\n      <- rtn <- Optional(\"Steve\")\n\n    nextName()\n      <- rtn <- Optional(\"Stephen\")\n\n    noName()\n      <- rtn <- Optional() of String\n\n  defines program\n\n    GuardInSafeScopeDemo()\n      stdout <- Stdout()\n\n      name <- firstName()\n      stdout.println(`before: name set=${name?}`)\n\n      if name?\n        stdout.println(\"checked: \" + name.get())\n        if replacement <- nextName()\n          stdout.println(\"replacement: \" + replacement.get())\n        alias <- noName()\n        alias := nextName()\n        moved <- alias?\n        stdout.println(`moved=${moved} alias set=${alias?}`)\n        stdout.println(\"inside: name=\" + name.get())\n\n      if name := nextName() then name?\n        stdout.println(\"re-pointed: \" + name.get())\n\n      count <- 0\n      limit <- 2\n      current <- noName()\n      while current :=? nextName() then count < limit\n        stdout.println(\"loop: \" + current.get())\n        count++\n\n      stdout.println(\"Done\")","migrationContext":"Kotlin: a smart cast is silently lost when a var is reassigned; the error appears later, at the unchecked use. Rust: 'if let Some(v) = o' binds a separate value and 'o' cannot be reassigned while borrowed. Swift: 'if let' copies, so the original can change without affecting the binding. EK9 unwraps the original variable directly, so it rejects the store that would break the proof - an assignment statement or a nested construct's head.","keywords":["E08040","guard","if o?","optional","pre-flow","re-point","reassignment","safe access","while"],"primaryTopics":["guards","optional","safe access"],"typicalErrors":[{"error":"E08040","correct":"        if replacement <- nextName()\n          stdout.println(\"replacement: \" + replacement.get())","incorrect":"        if name := nextName() then name?\n          stdout.println(\"replacement: \" + name.get())","explanation":"Inside 'if name?' the name is proven safe to unwrap, and the inner head's ':=' always stores its value into name, so the code after the inner if would unwrap a value nothing checked (an empty one throws 'No such element'). Guard into a NEW variable instead. See ek9 -h E08040 for details."},{"error":"E08040","correct":"        alias := nextName()","incorrect":"        name := nextName()","explanation":"An assignment statement is a store too. Re-pointing name inside the scope that checked it is E08040; assign a different variable. See ek9 -h E08040 for details."}],"companions":[]}
{"id":1961,"category":"Control Flow","question":"Does ':=?' evaluate its right-hand side when the target is already set?","url":"https://ek9.io/qa/QA1961.html","alternatePhrasings":["Is the right of ':=?' evaluated lazily?","How do I record whether an assignment gave a value, but only the first time?","How do I combine assign-if-unset with a set-check?","When does ':=?' call the function on its right?","What does error E08095 mean for ':=?'?"],"answer":"No. The right-hand side of ':=?' runs ONLY when its target is unset (or not yet initialised). When the target is already set, ':=?' neither assigns nor evaluates the right side - so a call there is not made at all. That makes ':=?' the assignment form of the coalescing '?:': 'found :=? value' is 'found := found ?: value'.\n\nTHE TWO OPERATORS\n  count := value     ALWAYS assign value to count (an unset value included)\n  found :=? value    assign value to found ONLY if found is unset; otherwise value is not even evaluated\n\nRECORD WHETHER AN ASSIGNMENT GAVE A VALUE, BUT ONLY THE FIRST TIME\nAssign, then fall back with ':=?' on the set-check:\n  count := fetchCount()   // count is always assigned\n  found :=? count?        // found takes the result only if it had none\nAn assignment never yields a value in EK9, so the set-check is its own step.\n\nA ':=?' ON A TARGET THE COMPILER KNOWS IS SET IS AN ERROR\nIf flow analysis proves the target is already set ('found <- false' then 'found :=? count?'), the ':=?' can never apply, and the compiler rejects it (E08095).\n\nSee Q1960 for re-pointing inside a safe access block (E08040), Q79 for ':=?' and Q759 for assigning and then checking.","ek9Example":"#!ek9\ndefines module qa.flow.guard.assignifunset\n\n  defines function\n\n    fetchCount()\n      <- rtn as Integer: 42\n\n    alreadyFound()\n      <- rtn as Boolean: false\n\n  defines program\n\n    GuardThenAssignIfUnsetDemo()\n      stdout <- Stdout()\n\n      count <- Integer()\n      found <- Boolean()\n      stdout.println(`before: count set=${count?} found set=${found?}`)\n\n      count := fetchCount()\n      found :=? count?\n      stdout.println(`after: count=${count} count set=${count?} found=${found}`)\n\n      found :=? alreadyFound()\n      stdout.println(`second: found=${found}`)\n\n      stdout.println(\"Done\")","migrationContext":"C#: 'found ??= Compute()' only evaluates Compute() when found is null, the same laziness as EK9's ':=?'. Kotlin: 'found = found ?: compute()'. JavaScript: 'found ??= compute()'. EK9: 'found :=? compute()' - and because an assignment never yields a value, a set-check of another assignment is written as its own step: 'count := fetchCount()' then 'found :=? count?'.","keywords":[":=?","E08095","assign if unset","boolean","conditional evaluation","lazy","set check"],"primaryTopics":["assign-if-unset","assignment operators"],"typicalErrors":[{"error":"E08095","correct":"      found <- Boolean()","incorrect":"      found <- false","explanation":"Declared with a value, found is provably set, so the following 'found :=? count?' can never apply. Declare it unset ('Boolean()') when ':=?' is meant to fill it. See ek9 -h E08095 for details."}],"companions":[]}
{"id":1962,"category":"Control Flow","question":"How do I run a block only when an EXISTING variable gets a set value?","url":"https://ek9.io/qa/QA1962.html","alternatePhrasings":["Why does 'if reading := fetchReading()' need a 'then'?","How do I re-point a variable in an if head and check the new value?","How do I guard a block on an existing variable getting a value?","Why does a while loop re-check the variable on every pass?","What replaced the ?= guard in EK9?"],"answer":"Assign it with ':=' and check the new value in the condition: 'if reading := fetchReading() then reading?'. ':=' always assigns - an unset value included, overwriting what reading held - and checks nothing, so the check is written out after 'then'. The body runs only when reading is now set. (EK9 has no '?=' guard; this explicit form replaced it.)\n\n  if reading := fetchReading() then reading?\n    // runs only when reading is now set\n\nA ':=' head with no condition is E07340: an assignment pre-flow checks nothing, so it needs 'then' (or 'with') and a condition.\n\nWITH A FURTHER CONDITION\nPut the set-check first and PARENTHESISE the rest: 'then sample? and (taken < wanted)'. 'and', 'or' and 'xor' share one precedence level, so 'sample? and a or b' would read as '(sample? and a) or b' and could enter with sample unset.\n\nIN A LOOP\nThe pre-flow runs ONCE, before the loop; the condition (set-check included) is tested on every pass:\n  while sample := firstSample() then sample? and (taken < wanted)\n\nSWITCH, FOR, TRY AND DO\nTheir heads have no condition to carry the check, so assign first and wrap the construct: 'reading := fetchReading()' then 'if reading?' around the switch. If the variable is only used inside, a declaration guard does it in one head: 'switch r <- fetchReading()'.\n\nSee Q1960 for re-pointing inside a safe access block (E08040), Q1963 for why a ':=' head is not a guard and Q74 for guard variables in if.","ek9Example":"#!ek9\ndefines module qa.flow.guard.declarationoverguard\n\n  defines function\n\n    fetchReading()\n      <- rtn as Integer: 21\n\n    fetchNoReading()\n      <- rtn as Integer: Integer()\n\n    firstSample()\n      <- rtn as Integer: 10\n\n  defines program\n\n    HeadGuardDemo()\n      stdout <- Stdout()\n\n      reading <- 0\n      stdout.println(`before: reading=${reading}`)\n\n      if reading := fetchReading() then reading?\n        stdout.println(`guarded body: reading=${reading}`)\n      stdout.println(`after a set value: reading set=${reading?}`)\n\n      bodyRan <- false\n      if reading := fetchNoReading() then reading?\n        bodyRan: true\n      stdout.println(`after an unset value: body ran=${bodyRan} reading set=${reading?}`)\n\n      sample <- 0\n      taken <- 0\n      wanted <- 2\n      while sample := firstSample() then sample? and (taken < wanted)\n        stdout.println(`sample ${sample} taken ${taken}`)\n        taken++\n\n      stdout.println(\"Done\")","migrationContext":"Swift: 'if let v = f()' binds and tests in one step, but only a NEW name. Kotlin: 'x = f(); if (x != null)'. EK9: 'if x := f() then x?' re-points the existing variable and tests it in one head; for a new name, 'if v <- f()' declares and tests.","keywords":[":=","E07340","existing variable","guard","if head","pre-flow","re-point","set check","then"],"primaryTopics":["guards","control flow"],"typicalErrors":[{"error":"E07340","correct":"      if reading := fetchReading() then reading?","incorrect":"      if reading := fetchReading()","explanation":"A ':=' head assigns and checks nothing, so on its own it decides nothing; give it a condition - the set-check of the new value. See ek9 -h E07340 for details."}],"companions":[]}
{"id":1963,"category":"Control Flow","question":"Why can't I call get() on an Optional assigned with ':=' in an if head?","url":"https://ek9.io/qa/QA1963.html","alternatePhrasings":["Why is 'if name := nextName() then ready' followed by name.get() error E08030?","Does ':=' in an if, while or try head check the value?","Which pre-flow operators make an Optional safe to unwrap?","How do I make an Optional assigned in an if head safe to unwrap?"],"answer":"Because ':=' in a head is a plain assignment: it checks nothing. Only a GUARD pre-flow skips the body when its variable is unset, and only a guard - or a set-check in the condition - proves the variable safe to unwrap in the body:\n\n  <-    declare a new variable, enter only if it is set\n  :=?   assign only if unset, enter only if it is set\n  :=    assign an existing variable - no check at all\n\n'if name := nextName() then ready' runs its body whenever ready is true, even if nextName() gave an empty Optional, so name.get() there is E08030 (it once compiled and threw 'No such element'). The same holds for 'while name := ...' and 'try name := ...'.\n\nPut the set-check into the condition, before the rest:\n  if name := nextName() then name? and ready\n    stdout.println(name.get())\nA set-check proves the unwrap safe when it is a top-level 'and' operand of the condition, whatever the other operands are - 'name? and ready' and 'name? and (count < limit)' both work, and 'name? and name.get() <> \"\"' is safe too, because 'and' only evaluates its right side when the left is true. A check under 'or' or 'not' proves nothing; for that, nest an 'if'.\n\nSee Q1960 for re-pointing inside a safe access block (E08040), Q1962 for re-pointing and checking in a head and Q84 for Optional's operations.","ek9Example":"#!ek9\ndefines module qa.flow.guard.assignmenthead\n\n  defines function\n\n    nextName()\n      <- rtn <- Optional(\"Steve\")\n\n    isReady()\n      <- rtn as Boolean: true\n\n  defines program\n\n    AssignmentHeadDemo()\n      stdout <- Stdout()\n\n      name <- Optional() of String\n      ready <- isReady()\n      stdout.println(`before: name set=${name?}`)\n\n      if name := nextName() then name? and ready\n        stdout.println(\"name: \" + name.get())\n      stdout.println(`after: name set=${name?}`)\n\n      stdout.println(\"Done\")","migrationContext":"Java: 'if ((o = next()).isPresent() && ready)' tests explicitly; assigning without the test leaves get() unchecked. Swift: 'if let' tests, a plain assignment before 'if' does not. EK9 keeps every pre-flow in the same head position and lets only the guard forms - or an explicit set-check in the condition - prove an Optional safe.","keywords":[":=",":=?","E08030","if head","optional","pre-flow","safe access","then","unwrap"],"primaryTopics":["guards","optional","safe access"],"typicalErrors":[{"error":"E08030","correct":"      if name := nextName() then name? and ready","incorrect":"      if name := nextName() then ready","explanation":"A ':=' head assigns without checking, so name might be an empty Optional when the body calls name.get(). Check it in the condition - 'then name? and ready' - so the body runs only when name is now set. See ek9 -h E08030 for details."}],"companions":[]}
{"id":1980,"category":"Control Flow","question":"What do <-, :=? and := each do in the head of an if, switch, for, while or try?","url":"https://ek9.io/qa/QA1980.html","alternatePhrasings":["Show the guard operators side by side in an if statement","Does ':=' in a head overwrite a variable that is already set when the new value is unset?","When does :=? call the right-hand side in a control-flow head?","Why does 'if x := f()' need a 'then' condition?","Which pre-flow keeps the old value and which one replaces it?"],"answer":"The part of a control-flow head before 'then'/'with' is the pre-flow. It runs exactly ONCE, before the body. Two operators there GUARD (the body is skipped when the variable ends up unset); ':=' does not, so it takes a condition after 'then' - and the explicit 'then x?' makes it re-point and guard.\n\n<- DECLARE AND GUARD\n  if fresh <- fetchReading()\nDeclares a NEW variable scoped to the construct and enters only if it is set.\n\n:= THEN x? - RE-POINT, THEN CHECK\n  if latest := fetchNothing() then latest?\nAssigns an EXISTING variable every time - an unset result included, so a value it held before is REPLACED - and the explicit check enters only if it is set. Here latest was 42 and is unset afterwards: the else branch runs.\n\n:=? FILL IF UNSET, THEN GUARD\n  if cache :=? expensiveReading()\nLooks at the TARGET first. If cache is unset, expensiveReading() is called and assigned; if cache is already set it is kept and expensiveReading() is NOT called. Either way the body runs when cache is set afterwards - so the second ':=?' enters the body with the value it already had.\n\n:= WITH ANOTHER CONDITION\n  if count := fetchNothing() then count < threshold\nAssigns with no set-check at all, so the head needs its own condition after 'then' (without one it is E07340). An unset count makes 'count < threshold' unset, which does not enter the body. To check the value AND a condition, put the check first and parenthesise the rest: 'then count? and (count < threshold)'.\n\nCHOOSING\n- a new variable, used only when set: <-\n- an existing variable, take the new value and enter only if set: := then x?\n- an existing variable, keep a value it already has: :=?\n- a setup step with your own condition: :=\n\nThe same operators work in switch, for, while, do/while and try heads (and their expression forms); a switch, for, try or do head has no condition, so there re-point first and wrap the construct in 'if x?'. The variable a ':=?' or ':=' pre-flow assigns keeps its value after the construct. See Q1981 for assigning and then checking outside a head, Q1982 for the misuses the compiler rejects, Q1983 for guards in loops, Q1023 and Q1024 for switch.","ek9Example":"#!ek9\ndefines module qa.controlflow.guardfamily.head\n\n  defines function\n\n    fetchReading()\n      <- rtn as Integer: 42\n\n    fetchNothing()\n      <- rtn as Integer: Integer()\n\n    expensiveReading()\n      <- rtn as Integer: 99\n      Stdout().println(\"  expensiveReading() called\")\n\n  defines program\n\n    GuardFamilyInAHead()\n      stdout <- Stdout()\n      threshold <- fetchReading()\n\n      //<- declares a NEW variable and enters only if it is set\n      if fresh <- fetchReading()\n        stdout.println(`<- body: fresh=${fresh}`)\n\n      //':=' ALWAYS assigns an existing variable; 'then latest?' enters only if it is now set\n      latest <- fetchReading()\n      stdout.println(`re-point before: latest? ${latest?}`)\n      if latest := fetchNothing() then latest?\n        stdout.println(\"re-point body ran\")\n      else\n        stdout.println(\"re-point else: the unset result was assigned\")\n      stdout.println(`re-point after: latest? ${latest?}`)\n\n      //:=? assigns only if the variable is unset - and only then calls the right side\n      cache <- fetchNothing()\n      stdout.println(`:=? before: cache? ${cache?}`)\n      if cache :=? expensiveReading()\n        stdout.println(`:=? body: cache=${cache}`)\n      if cache :=? expensiveReading()\n        stdout.println(`:=? body again: cache=${cache} - kept, not recomputed`)\n\n      //:= is a blind assignment: no set-check, so it needs its own condition after 'then'\n      count <- fetchReading()\n      if count := fetchNothing() then count < threshold\n        stdout.println(\":= body ran\")\n      else\n        stdout.println(\":= else: an unset count makes the condition unset, not true\")\n      stdout.println(`:= after: count? ${count?}`)","migrationContext":"Java: 'x = f(); if (x != null) {...}' is EK9's 'if x := f() then x?', and :=? has no one-line form ('if (cache == null) cache = compute(); if (cache != null) {...}'). Kotlin: 'f()?.let { x = it; ... }' assigns only a non-null value - EK9's ':=' assigns the unset value too, so the variable always reflects the latest call. Go: 'if x := f(); x != nil {}' declares like <- but needs the explicit check. EK9: one head shape in every control-flow construct.","keywords":[":=",":=?","<-","assign","for","guard","head","if","lazy","overwrite","pre-flow","switch","try","unset","while"],"primaryTopics":["guard operators","pre-flow","control flow guards"],"typicalErrors":[{"error":"E07340","correct":"      if count := fetchNothing() then count < threshold","incorrect":"      if count := fetchNothing()","explanation":"':=' in a head is a blind assignment, not a guard, so an 'if' with only ':=' has no condition at all. Add 'then <condition>' - 'then count?' to gate on the value being set. See ek9 -h E07340 for details."},{"error":"E50001","correct":"      if fresh <- fetchReading()","incorrect":"      if fresh := fetchReading() then fresh?","explanation":"':=' assigns an EXISTING variable; it never declares one, so 'fresh' does not resolve. Use '<-' to declare a new variable in the head. See ek9 -h E50001 for details."},{"error":"E50050","correct":"      if latest := fetchNothing() then latest?","incorrect":"      if latest <- fetchNothing()","explanation":"'<-' always DECLARES, and 'latest' already exists, so the head would declare it a second time - EK9 does not shadow. To assign an existing variable and guard on it, use 'if latest := fetchNothing() then latest?'. See ek9 -h E50050 for details."}],"companions":[]}
{"id":1981,"category":"Control Flow","question":"What does 'x := f()' do when f() returns an unset value, and how do I keep the old value instead?","url":"https://ek9.io/qa/QA1981.html","alternatePhrasings":["Does ':=' assign an unset result?","How do I assign and then know whether the new value is set?","How do I assign a new value only when it is set and otherwise keep the old one?","Why does '??' not keep my old value when the new one is unset?","What is the difference between :=, ?: and :=? for updating a variable?"],"answer":"':=' ALWAYS assigns the right side to the existing variable - an unset result included. An assignment never yields a value, so to know whether the new value is set, check it afterwards:\n\n  latest := fetchReading()      // latest = 42\n  gotOne <- latest?             // true\n  latest := fetchNothing()      // latest is now UNSET\n  gotAnother <- latest?         // false\n\nlatest always reflects the most recent call. In a control-flow head the same check gates the body: 'if latest := f() then latest?'.\n\nTO KEEP THE OLD VALUE WHEN THE NEW ONE IS UNSET, USE ?:\n  kept := fetchNothing() ?: kept\n'?:' gives its left side when it is SET, otherwise its right - so kept stays 42.\n\n?? DOES NOT DO THIS\n  passed := fetchNothing() ?? passed\n'??' only replaces an ABSENT value (no object at all). An unset Integer is present, so '??' passes it straight through and passed becomes unset.\n\nTO FILL A VARIABLE ONLY WHILE IT IS UNSET, USE :=?\n  fallback :=? fetchReading()\n':=?' looks at the target, not the new value: it assigns only when fallback is unset, and does not even call fetchReading() otherwise.\n\nCHOOSING\n  always take the new value, know if it is set   x := f() then ok <- x?\n  take the new value only if it is set            x := f() ?: x\n  set x only if it has no value yet               x :=? f()\n\nSee Q1980 for the pre-flow operators in a control-flow head, Q1982 for the combinations the compiler rejects, Q1059 for ?? versus ?:, Q79 for :=?.","ek9Example":"#!ek9\ndefines module qa.controlflow.guardfamily.expression\n\n  defines function\n\n    fetchReading()\n      <- rtn as Integer: 42\n\n    fetchNothing()\n      <- rtn as Integer: Integer()\n\n  defines program\n\n    GuardAsAnExpression()\n      stdout <- Stdout()\n\n      //':=' ALWAYS assigns; 'latest?' afterwards says whether the new value is set\n      latest <- fetchNothing()\n      stdout.println(`before: latest? ${latest?}`)\n\n      latest := fetchReading()\n      gotOne <- latest?\n      stdout.println(`set result: gotOne=${gotOne} latest=${latest}`)\n\n      latest := fetchNothing()\n      gotAnother <- latest?\n      stdout.println(`unset result: gotAnother=${gotAnother} latest? ${latest?}`)\n\n      //To KEEP the old value when the new one is unset, use ?: - left if set, otherwise right\n      kept <- fetchReading()\n      kept := fetchNothing() ?: kept\n      stdout.println(`?: keeps the old value: kept=${kept}`)\n\n      //?? is not the same: it only replaces an ABSENT value, and an unset one is present\n      passed <- fetchReading()\n      passed := fetchNothing() ?? passed\n      stdout.println(`?? passes the unset value through: passed? ${passed?}`)\n\n      //When the variable may be unset and should only be filled, use :=?\n      fallback <- fetchNothing()\n      fallback :=? fetchReading()\n      stdout.println(`:=? fills an unset variable: fallback=${fallback}`)","migrationContext":"Kotlin: 'x = f() ?: x' is the keep-old idiom and maps to EK9's '?:'; 'f()?.let { x = it }' (assign only when non-null) has no single EK9 operator by design - write 'x := f() ?: x'. C#/JavaScript/Swift: '??' is the keep-old idiom there, but EK9's '??' only tests for an ABSENT value, so an unset result passes through it. EK9 has no assignment that also reports set-ness: assign with ':=', then test with '?'.","keywords":[":=",":=?","?:","??","Boolean","always assigns","assign then check","coalescing","default","elvis","keep old value","unset"],"primaryTopics":["assign then check","coalescing operators","keeping a value"],"typicalErrors":[{"error":"E08095","correct":"      fallback <- fetchNothing()","incorrect":"      fallback <- 0","explanation":"Starting fallback from a literal makes it always set, so the ':=?' below can never assign. ':=?' is for a variable that may be unset; to replace a set value use ':=', or '?:' to keep it when the new one is unset. See ek9 -h E08095 for details."}],"companions":[]}
{"id":1982,"category":"Control Flow","question":"Which ways of combining the guard operators does the compiler reject, and what should I write instead?","url":"https://ek9.io/qa/QA1982.html","alternatePhrasings":["Which pre-flow mistakes does the EK9 compiler reject?","Why is a ':=' head with no condition a compile error?","Can I re-point an Optional inside 'if opt?'?","Why does ':=?' on a variable I just initialised say it can never apply?","What are the common mistakes with :=, :=? and <- in a control-flow head?"],"answer":"Each pre-flow operator says one thing, and three combinations say something you did not mean. The compiler rejects them.\n\n1. :=? ON A VARIABLE THAT IS ALWAYS SET (E08095)\n  setting <- 7\n  setting :=? fetchReading()     // can never assign\n':=?' fills an UNSET variable. When the compiler can prove the variable is set, the ':=?' can never apply (and its right side is never even evaluated). Start from a value that may be unset ('setting <- fetchNothing()'), or assign with ':='.\n\n2. A ':=' HEAD WITH NO CONDITION (E07340)\n  if latest := fetchReading()\n':=' in a head assigns and checks nothing, so an 'if' with only ':=' has no condition at all. To enter only when the new value is set, say so: 'if latest := fetchReading() then latest?'. An assignment never yields a value, so outside a head the check is its own step: 'latest := fetchReading()' then 'found := latest?'.\n\n3. RE-POINTING A CHECKED OPTIONAL (E08040)\nInside 'if current?', writing 'if current := fetchName() then current?' (or a plain 'current := fetchName()') re-points current. 'if current?' made current.get() safe in that block - for THAT value. The assignment may put an empty Optional there. Take a NEW variable ('if replacement <- fetchName()').\n\nThe program shows each correct form. See Q1980 for the pre-flow operators, Q1981 for assigning and then checking, Q79 for :=?, Q86 for Optional safety.","ek9Example":"#!ek9\ndefines module qa.controlflow.guardfamily.misuses\n\n  defines function\n\n    fetchReading()\n      <- rtn as Integer: 42\n\n    fetchNothing()\n      <- rtn as Integer: Integer()\n\n    fetchName()\n      <- rtn as Optional of String: Optional(\"Steve\")\n\n  defines program\n\n    GuardMisusesFixed()\n      stdout <- Stdout()\n\n      //:=? only means something when the variable may be unset\n      setting <- fetchNothing()\n      setting :=? fetchReading()\n      stdout.println(`setting=${setting}`)\n\n      //An assignment never yields a value - assign, then check the new value\n      latest <- fetchNothing()\n      found <- Boolean()\n      latest := fetchReading()\n      found := latest?\n      stdout.println(`found=${found} latest=${latest}`)\n\n      //A ':=' head checks nothing - the explicit 'then latest?' is the gate\n      if latest := fetchReading() then latest?\n        stdout.println(`guarded: latest=${latest}`)\n\n      //Inside the block a check made safe, take a NEW variable rather than re-point the checked one\n      current <- fetchName()\n      if current?\n        if replacement <- fetchName()\n          stdout.println(`replacement: ${replacement.get()}`)\n        stdout.println(`current: ${current.get()}`)","migrationContext":"Kotlin: 'x = x ?: y' and 'if (y?.also { x = it } != null)' are legal and easy to get wrong; EK9 gives each intent its own operator and rejects the combinations that hide an assignment that never applies or a head that checks nothing. Java: re-assigning a variable after an isPresent() check compiles and fails at runtime with NoSuchElementException - in EK9 that is a compile error.","keywords":[":=",":=?","<-","E07340","E08040","E08095","Optional","always set","compile error","guard","misuse","never applies"],"primaryTopics":["guard misuse","guard operators","safe access"],"typicalErrors":[{"error":"E08095","correct":"      setting <- fetchNothing()","incorrect":"      setting <- 7","explanation":"'setting' starts with a literal, so it is always set and the ':=?' below it can never assign. ':=?' is for a variable that may be unset. See ek9 -h E08095 for details."},{"error":"E07340","correct":"      if latest := fetchReading() then latest?","incorrect":"      if latest := fetchReading()","explanation":"':=' in a head is a plain assignment that checks nothing, so an 'if' with only ':=' has no condition. Add 'then latest?' to enter only when the new value is set. See ek9 -h E07340 for details."},{"error":"E08040","correct":"        if replacement <- fetchName()\n          stdout.println(`replacement: ${replacement.get()}`)","incorrect":"        if current := fetchName() then current?\n          stdout.println(`replacement: ${current.get()}`)","explanation":"'if current?' made current.get() safe for the value it checked; the inner head's ':=' always assigns, so it could put an empty Optional in its place and the later current.get() would fail. Take a new variable instead. See ek9 -h E08040 for details."}],"companions":[]}
{"id":1983,"category":"Control Flow","question":"Does a guard in a while or do/while head run on every iteration?","url":"https://ek9.io/qa/QA1983.html","alternatePhrasings":["Is 'while x := next() then x?' re-evaluating next() each time round the loop?","How many times is the function in a while guard called?","Does an unset guard skip the first pass of a do/while loop?","How do I loop until a function returns an unset value?"],"answer":"No. The guard is the PRE-FLOW of the loop: it runs exactly ONCE, before the first iteration. After that only the condition after 'then' (or, for do/while, the condition after the closing 'while') is tested again.\n\n  while reading := feed.next() then reading? and (passes < limit)\n    passes++\nfeed.next() is called once. reading is assigned once (here 1) and stays 1 for every pass; the loop runs because the condition - 'reading?' and 'passes < limit' - is re-tested. Afterwards next() has been called exactly once.\n\nAN UNSET VALUE SKIPS THE LOOP ENTIRELY\nA do/while head has no condition to carry the check, so re-point first and wrap the loop:\n  reading := drained.next()\n  if reading?\n    do\n      skipped++\n    while skipped < limit\ndrained.next() is unset, so reading is assigned the unset value (':=' always assigns) and the body never runs - not even the first pass a do/while would otherwise always make. A '<-' or ':=?' guard in the head ('do r <- next()') skips it the same way.\n\nTO POLL UNTIL A VALUE IS UNSET\nPut the fetch in the body and test the variable in the condition:\n  arrival <- polled.next()\n  while arrival?\n    total += arrival\n    arrival := polled.next()\nThat calls next() until it comes back unset (four calls for three readings).\n\nThe same rule holds for every pre-flow (<-, :=?, :=), in for, switch and try heads too. See Q1980 for the pre-flow operators, Q77 and Q1150 for while guards, Q1355 for do/while.","ek9Example":"#!ek9\ndefines module qa.controlflow.guardfamily.loops\n\n  defines class\n\n    Feed\n      calls as Integer: 0\n      available as Integer?\n\n      default private Feed()\n\n      Feed()\n        -> readings as Integer\n        available: readings\n\n      next()\n        <- rtn as Integer: Integer()\n        calls++\n        if calls <= available\n          rtn: calls\n\n      callCount()\n        <- rtn as Integer: calls\n\n      default operator ?\n\n  defines program\n\n    GuardsInLoops()\n      stdout <- Stdout()\n      limit <- 3\n\n      //The guard is the pre-flow: it runs ONCE, before the loop; only 'then' is re-tested\n      feed <- Feed(limit)\n      reading <- Integer()\n      passes <- 0\n      while reading := feed.next() then reading? and (passes < limit)\n        passes++\n      stdout.println(`while re-point: passes=${passes} reading=${reading} next() calls=${feed.callCount()}`)\n\n      //An unset value skips the loop entirely - even a do/while's first pass\n      drained <- Feed(0)\n      skipped <- 0\n      reading := drained.next()\n      if reading?\n        do\n          skipped++\n        while skipped < limit\n      stdout.println(`do/while re-point unset: body ran ${skipped} times, reading? ${reading?}`)\n\n      //To POLL until a value is unset, fetch in the body and test the variable\n      polled <- Feed(limit)\n      total <- 0\n      arrival <- polled.next()\n      while arrival?\n        total += arrival\n        arrival := polled.next()\n      stdout.println(`polling: total=${total} next() calls=${polled.callCount()}`)","migrationContext":"Java: 'while ((line = reader.readLine()) != null)' re-runs the assignment on every iteration - EK9's pre-flow does not; it is setup, not polling. Python: 'while (item := next_item()) is not None:' likewise re-evaluates each time. In EK9 write the poll explicitly: fetch before the loop, test 'x?' in the condition, fetch again at the end of the body.","keywords":[":=","do","guard","iteration","loop","once","poll","pre-flow","skip","unset","while"],"primaryTopics":["while guards","pre-flow","loop guards"],"typicalErrors":[{"error":"E50050","correct":"      while reading := feed.next() then reading? and (passes < limit)","incorrect":"      while reading <- feed.next() then passes < limit","explanation":"'reading' already exists, and '<-' always declares a new variable - EK9 does not shadow. Assign the existing variable with ':=' and check it in the condition. See ek9 -h E50050 for details."}],"companions":[]}
{"id":2000,"category":"Functions and Methods","question":"If a parameter cannot be reassigned, is it read-only? How do aliasing and copying work in EK9?","url":"https://ek9.io/qa/QA2000.html","alternatePhrasings":["Does E08110 mean my function's parameters are read-only?","Can a function change an object the caller passed in?","Why does b++ also change a after b <- a?","How do I get an independent copy of a value instead of an alias?","What makes a parameter really read-only in EK9?"],"answer":"EK9 keeps reference semantics: a variable is a BINDING to an object, and '<-' or '=' to another variable binds a second name to the SAME object. After 'shared <- original', 'shared++' changes what 'original' shows. For an independent value, construct one: 'independent <- Integer(original)' (or copy into an existing object with ':=:').\n\nAn incoming parameter's BINDING cannot be reassigned - 'counter := 0' on a parameter is E08110. That is all E08110 says. The parameter is NOT read-only: in non-pure code the object it refers to can be mutated in place ('counter += 1', 'items.add(x)') and the caller sees the change; it can also be aliased and the alias reassigned.\n\nRead-only is what 'pure' provides. A pure function or method cannot mutate or reassign (E08100, E08120), and pure code may call only pure code (E08130), so the guarantee carries through everything it calls: 'doubled() as pure' may read its parameter but 'counter += 1' inside it is E08120.\n\nCopying: ':=:' copies a value INTO an existing object. Each type copies at its own level - a value copies its value, a List copies its element references - and the generated 'default operator :=:' is SHALLOW: each field of the target then refers to the source's object. A deep or selective copy is an 'operator :=:' you write (see Q2100).\n\nSee Q737 for reassignment in pure code and Q905 for mutating operators and aliasing.","ek9Example":"#!ek9\ndefines module qa.functions.bindingsandobjects\n\n  defines function\n\n    bump()\n      -> counter as Integer\n      counter += 1\n\n    doubled() as pure\n      -> counter as Integer\n      <- rtn as Integer: Integer()\n      rtn: counter * 2\n\n  defines program\n\n    BindingsAndObjects()\n      stdout <- Stdout()\n\n      original <- 1\n      shared <- original\n      shared++\n      stdout.println(`after shared++ original=${original} shared=${shared}`)\n\n      independent <- Integer(original)\n      independent++\n      stdout.println(`after independent++ original=${original} independent=${independent}`)\n\n      bump(original)\n      stdout.println(`after bump(original) original=${original}`)\n\n      stdout.println(`doubled(original)=${doubled(original)} original=${original}`)","migrationContext":"Java: parameters are references passed by value - reassigning one is legal but invisible to the caller, while mutating the object is visible; 'final' stops the reassignment only. Python: the same, with no 'final' at all. Kotlin: parameters are 'val' (no reassignment) but the object can still be mutated. C++: 'const T&' gives read-only access. EK9: the binding of a parameter is never reassignable (E08110); 'pure' is the read-only guarantee, enforced transitively.","keywords":["E08110","E08120","alias","aliasing","binding","copy","mutation","parameter","pure","read-only","reassignment","reference"],"primaryTopics":["parameter binding vs object","aliasing and copying"],"typicalErrors":[{"error":"E08110","correct":"      counter += 1","incorrect":"      counter := counter + 1","explanation":"A parameter's binding cannot be reassigned. Mutate the caller's object in place with '+=', or work on a local copy ('working <- Integer(counter)'). See ek9 -h E08110 for details."},{"error":"E08120","correct":"      rtn: counter * 2","incorrect":"      counter += 1","explanation":"A pure function may not mutate anything - that is what makes its parameters read-only. Compute into the returning variable instead. See ek9 -h E08120 for details."}],"companions":[]}
{"id":2001,"category":"Control Flow","question":"Does 'if allowed <- permitted()' run its body when permitted() returns false?","url":"https://ek9.io/qa/QA2001.html","alternatePhrasings":["How does a guard behave when the value is a Boolean?","Why is my if guard skipped when the Boolean is false?","Does a switch guard over a Boolean still reach case false?","Is 'then allowed' needed after 'if allowed <- check()'?"],"answer":"No. A guard normally enters when its value is SET, but over a Boolean an 'if' (or 'else if') guard enters only when the value is SET AND TRUE. 'if allowed <- permitted(user)' skips the body for false just as it does for an unset result, so no trailing 'then allowed' is needed. The same holds for ':=?' in an if head: 'if decision :=? permitted(false)' fills decision with false and skips the body.\n\nIt is deliberately narrow:\n- only 'if' and 'else if'; a 'switch' keeps enter-if-set, so its 'case false' stays reachable, and so do 'while', 'do', 'for' and 'try'\n- only '<-' and ':=?'; a ':=' head is blind (E07340 without a condition)\n- only a variable DECLARED Boolean; a type that merely promotes to Boolean keeps enter-if-set\n\nAn unset Boolean skips the body as any unset value does.\n\nSee Q74 for the guard in an if and Q1980 for the head forms side by side.","ek9Example":"#!ek9\ndefines module qa.controlflow.booleantruthguard\n\n  defines function\n\n    permitted()\n      -> allowed as Boolean\n      <- rtn as Boolean: allowed\n\n    unknown()\n      <- rtn as Boolean: Boolean()\n\n  defines program\n\n    BooleanTruthGuard()\n      stdout <- Stdout()\n\n      if granted <- permitted(true)\n        stdout.println(`if over true: entered, granted=${granted}`)\n\n      if refused <- permitted(false)\n        stdout.println(`if over false: entered, refused=${refused}`)\n      else\n        stdout.println(\"if over false: skipped\")\n\n      if missing <- unknown()\n        stdout.println(`if over unset: entered, missing=${missing}`)\n      else\n        stdout.println(\"if over unset: skipped\")\n\n      decision <- Boolean()\n      if decision :=? permitted(false)\n        stdout.println(`fill with false: entered, decision=${decision}`)\n      else\n        stdout.println(`fill with false: skipped, decision set=${decision?}`)\n\n      switch answer <- permitted(false)\n        case true\n          stdout.println(\"switch over false: case true\")\n        case false\n          stdout.println(`switch over false: case false, answer=${answer}`)\n        default\n          stdout.println(\"switch over false: default\")","migrationContext":"Swift: 'if let allowed = permitted()' enters for false (it only unwraps). Kotlin: 'permitted()?.let { }' enters for false. Java: 'if (Boolean.TRUE.equals(permitted()))' is the null-safe truth test. EK9: the guard over a Boolean is that null-safe truth test, in an if.","keywords":["Boolean","assign-if-unset","case false","declaration guard","else if","guard","if","switch","truth"],"primaryTopics":["Boolean truth guard","guards in if and switch"],"typicalErrors":[{"error":"E07340","correct":"      if decision :=? permitted(false)","incorrect":"      if decision := permitted(false)","explanation":"':=' in a head is a blind assignment and checks nothing, so the if has no condition. Use ':=?' (which fills an unset target and, over a Boolean in an if, enters only when true) or add 'then decision'. See ek9 -h E07340 for details."}],"companions":[]}
{"id":2002,"category":"Control Flow","question":"Can I use a '<-' guard's variable in the else block of the same if?","url":"https://ek9.io/qa/QA2002.html","alternatePhrasings":["What does E08270 mean?","Why is reading the guard variable in the else an error?","How do I give a guard variable a value in a later else if?","Why is 'else if name?' always false after 'if name <- lookup()'?"],"answer":"Not to read it before you assign it. Every later 'else if' and 'else' of an 'if name <- lookup()' chain runs BECAUSE the guard failed - name was unset - so reading name there finds nothing: that read is E08270, and testing it with 'name?' is always false (E08089).\n\nA WRITE stays legal, and a read after the write is fine. Re-point it in a later head and check it: 'else if name := lookup(\"fallback\") then name?'.\n\nThe rule covers a guard that is the whole condition. With a 'then' condition, or over a Boolean (unset OR false), the later block may see a value, so it is not checked.\n\nSee Q74 for the guard in an if and Q2001 for guards over a Boolean.","ek9Example":"#!ek9\ndefines module qa.controlflow.guardvariableinelse\n\n  defines function\n\n    lookup()\n      -> key as String\n      <- rtn as String: key\n\n  defines program\n\n    GuardVariableInElse()\n      stdout <- Stdout()\n\n      if found <- lookup(String())\n        stdout.println(`found ${found}`)\n      else\n        stdout.println(\"nothing found\")\n\n      if name <- lookup(String())\n        stdout.println(`name ${name}`)\n      else if name := lookup(\"fallback\") then name?\n        stdout.println(`using ${name}`)\n      else\n        stdout.println(\"no name at all\")","migrationContext":"Swift: 'if let name = lookup() { } else { }' - name is not even in scope in the else. Kotlin: 'lookup()?.let { } ?: run { }' - same. EK9 keeps the variable in scope for the whole chain so a later head can assign it, and rejects the reads that can only find nothing.","keywords":["E08089","E08270","declaration guard","else","else if","guard","scope","unset"],"primaryTopics":["guard variable in later else blocks"],"typicalErrors":[{"error":"E08270","correct":"        stdout.println(\"nothing found\")","incorrect":"        stdout.println(`nothing found for ${found}`)","explanation":"The else runs because 'found' was unset, so reading it there finds nothing. Use it inside the guarded block, or assign it first. See ek9 -h E08270 for details."},{"error":"E08089","correct":"      else if name := lookup(\"fallback\") then name?","incorrect":"      else if name?","explanation":"In a later block of the chain the guard's variable is always unset, so checking it is always false. Re-point it first: 'else if name := lookup(...) then name?'. See ek9 -h E08089 for details."}],"companions":[]}
{"id":2003,"category":"Data Flow Safety","question":"Does ':=?' initialise a variable declared with no value, even when the value it assigns is unset?","url":"https://ek9.io/qa/QA2003.html","alternatePhrasings":["Can I use a variable after 'x as Integer?' and 'x :=? f()'?","What happens when ':=?' assigns an unset value?","Why is my ':=?' inside an if not enough to initialise the variable?","Is a variable filled by ':=?' ever absent?"],"answer":"Yes - where the ':=?' always runs. A variable declared with no value ('x as Integer?') counts as unset, so 'x :=? f()' assigns whatever f() returned. That is an object - present, maybe unset, never absent - so x is initialised and every later use is legal: arithmetic on an unset Integer gives an unset result, not a crash.\n\nIt counts only where it always runs: a statement, the FIRST 'if' pre-flow, a 'while', 'for', 'switch' or 'try' pre-flow, a 'do' body, a 'finally'. An 'else if' pre-flow, an if/case/loop body, a try body and a catch may not run, so a variable filled only there is E08020 at its later use. 'if third :=? fetch(true)' in the head initialises third; the same ':=?' inside the if body does not.\n\nOn a variable the compiler can prove is already set, ':=?' can never apply (E08095).\n\nSee Q79 for ':=?' and Q29 for unset variables.","ek9Example":"#!ek9\ndefines module qa.dataflow.assignifunsetinitialises\n\n  defines function\n\n    fetch()\n      -> wanted as Boolean\n      <- rtn as Integer: Integer()\n      if wanted\n        rtn: 42\n\n  defines program\n\n    AssignIfUnsetInitialises()\n      stdout <- Stdout()\n\n      first as Integer?\n      first :=? fetch(true)\n      stdout.println(`first set=${first?} value=${first}`)\n\n      second as Integer?\n      second :=? fetch(false)\n      stdout.println(`second set=${second?}`)\n\n      total <- second + 1\n      stdout.println(`second + 1 set=${total?}`)\n\n      third as Integer?\n      if third :=? fetch(true)\n        stdout.println(`third filled by the if head: ${third}`)\n      stdout.println(`third after the if set=${third?}`)","migrationContext":"Kotlin: 'var x: Int? = null; x = x ?: f()' leaves x null when f() returns null. C#: 'x ??= F()' the same. EK9 has no null: ':=?' always stores an object, which may be unset, so the variable is initialised either way.","keywords":[":=?","E08020","absent","assign-if-unset","definite assignment","initialise","uninitialised","unset"],"primaryTopics":[":=? initialises","definite initialisation"],"typicalErrors":[{"error":"E08020","correct":"      if third :=? fetch(true)\n        stdout.println(`third filled by the if head: ${third}`)\n","incorrect":"      if fetch(true)?\n        third :=? fetch(true)\n","explanation":"Inside the if body ':=?' may not run, so 'third' might still have no value when it is read afterwards. Put ':=?' where it always runs - the if head or a statement. See ek9 -h E08020 for details."}],"companions":[]}
{"id":2004,"category":"Operators and Expressions","question":"What are the names of EK9's '?' operators - '?', '<-', ':=', ':=?', '?:' and '<?'?","url":"https://ek9.io/qa/QA2004.html","alternatePhrasings":["What is the difference between ':=?' and ':='?","Is there a '?=' operator in EK9?","What do I call ':=?' and '?:'?","Which of EK9's operators check whether a value is set?"],"answer":"One name per symbol, used everywhere in EK9:\n- 'x?' is the SET-CHECK: true when x holds a meaningful value\n- '<-' is a DECLARATION; in an if/switch/for/while/do/try head it is a DECLARATION GUARD (enter only if set)\n- ':=' (also '=' and ':') is an ASSIGNMENT; in a head it is blind, so add 'then x?'\n- ':=?' is ASSIGN-IF-UNSET: it fills the target only if it is unset ('a :=? b' is 'a := a ?: b')\n- '?:' is COALESCE (Elvis): the left if set, otherwise the right, evaluated only then\n- '<?' '<=?' '>?' '>=?' are COMPARISON COALESCING: they return one of their operands - the set one, or the lesser/greater\n\nA GUARD is the idea 'enter only if set' - like Swift's 'if let'. There is no '?=' operator and no assignment yields a value: to know whether a new value is set, assign and then check, 'x := f()' then 'ok <- x?'. A coalescing operator mixed with another binary operator must be parenthesised (E08269).\n\nSee Q79 for ':=?', Q1091 for '?:', Q939 for the comparison coalescing operators and Q1980 for the head forms.","ek9Example":"#!ek9\ndefines module qa.operators.questionmarknames\n\n  defines function\n\n    textOf()\n      -> text as String\n      <- rtn as String: text\n\n    countOf()\n      -> count as Integer\n      <- rtn as Integer: count\n\n  defines program\n\n    QuestionMarkNames()\n      stdout <- Stdout()\n      maybe <- textOf(String())\n\n      //set-check: x?\n      stdout.println(`set-check: maybe? is ${maybe?}`)\n\n      //declaration guard: <- in a head\n      if guarded <- textOf(\"guarded\")\n        stdout.println(`declaration guard entered with ${guarded}`)\n\n      //assign-if-unset: :=? fills only an unset target\n      maybe :=? \"filled\"\n      stdout.println(`assign-if-unset: maybe is ${maybe}`)\n\n      //assignment: := is blind, so check the new value with then x?\n      entered <- false\n      if maybe := textOf(String()) then maybe?\n        entered: true\n      stdout.println(`assignment re-pointed maybe: set is ${maybe?}, entered is ${entered}`)\n\n      //coalesce (Elvis): the left if set, otherwise the right\n      chosen <- maybe ?: \"fallback\"\n      stdout.println(`coalesce: ${chosen}`)\n\n      //comparison coalescing: returns an operand - the set one, or the lesser\n      lower <- countOf(Integer()) <? countOf(7)\n      stdout.println(`comparison coalescing: ${lower}`)","migrationContext":"C#/JavaScript: '??=' is EK9's ':=?' and '??' is close to EK9's '?:' (EK9 also falls back for an UNSET value). Kotlin: '?:' is the same name and shape. Swift: 'if let' is EK9's declaration guard. Groovy's '?=' does not exist in EK9.","keywords":[":=",":=?","<-","<?","?","?:","Elvis","assign-if-unset","coalesce","comparison coalescing","declaration guard","names","set-check"],"primaryTopics":["names of the ? operators"],"typicalErrors":[{"error":"E07340","correct":"      if maybe := textOf(String()) then maybe?","incorrect":"      if maybe := textOf(String())","explanation":"An assignment in a head is blind - it checks nothing - so the if has no condition. Add 'then maybe?' to enter only when the new value is set. See ek9 -h E07340 for details."},{"error":"E08269","correct":"      chosen <- maybe ?: \"fallback\"","incorrect":"      chosen <- maybe ?: \"fallback\" + \"!\"","explanation":"'?:' binds tighter than '+', so this would be (maybe ?: \"fallback\") + \"!\". Parenthesise whichever is meant. See ek9 -h E08269 for details."}],"companions":[]}
{"id":2020,"category":"Generics","question":"How do I write a generic type that can be parameterised with an abstract type?","url":"https://ek9.io/qa/QA2020.html","alternatePhrasings":["Holder(square) fails with E10030 when square is declared as an abstract Shape","My generic's no-arg constructor calls T() and now I cannot use it with an abstract type","Why is a private constructor that is never called still E10030 for an abstract type parameter?","Can a user generic hold a value whose declared type is an abstract class or a trait?"],"answer":"Give the generic a PRIVATE no-argument constructor that leaves the T property unset (it must not call T()), and make every public construction go through the inferred-type constructor that takes a value:\n\n  Holder of type T\n    item as T?\n\n    default private Holder()\n\n    Holder()\n      -> initial as T\n      item: initial\n\nThen 'Holder(square)' with 'square as Shape' (an abstract Shape) gives a 'Holder of Shape' that compiles and runs.\n\nWHY T() FAILS FOR AN ABSTRACT TYPE\nA generic over 'T?' needs a no-argument constructor (E06040), and the usual way to fill one is 'item: T()' - an unset T. But T() is a constructor call on whatever type parameterises the generic, and an abstract type has no constructor that may be called directly. So as soon as the generic is parameterised with Shape, every T() in it becomes 'Shape()' and is rejected with E10030 - even in a PRIVATE constructor that nothing calls, because the whole parameterised type is checked, not just the parts that are used.\n\nWHY THE PRIVATE NO-ARG IS SAFE\nThe private constructor leaves 'item' without a value, so it may only be reached through this() from a constructor that then sets 'item'. Inside the generic, a construction call that reaches it directly - 'Holder() of T' - is E08264; outside it, 'Holder() of Shape' is already E06180 (not accessible). So no code can ever obtain a Holder whose item was never set, and nothing ever needs T().\n\nThe same Holder still works with concrete types: 'Holder(\"Steve\")' is a Holder of String.\n\nSee Q1347 for when a generic must use 'default private'. See Q841 for abstract generics such as Iterator. See Q1404 for E08264.","ek9Example":"#!ek9\ndefines module qa.genericsdeep.abstractparameterisation\n\n  defines class\n\n    Shape as abstract\n      area() as abstract\n        <- rtn as Float?\n\n      default operator ?\n\n    Square extends Shape\n      side as Float?\n\n      default private Square()\n\n      Square()\n        -> sideLength as Float\n        side: sideLength\n\n      override area()\n        <- rtn as Float: side * side\n\n      default operator ?\n\n    <?-\n      Usable with abstract and concrete types alike: the no-arg constructor is private and does\n      not call T(), so nothing here needs T to be constructable.\n    -?>\n    Holder of type T\n      item as T?\n\n      default private Holder()\n\n      Holder()\n        -> initial as T\n        item: initial\n\n      get()\n        <- rtn as T: item\n\n      duplicate()\n        <- rtn as Holder of T: Holder(item)\n\n      default operator ?\n\n  defines program\n\n    AbstractParameterisation()\n      stdout <- Stdout()\n\n      square as Shape: Square(3.0)\n      shapes <- Holder(square)\n      stdout.println(`area: ${shapes.get().area()}`)\n\n      copied <- shapes.duplicate()\n      stdout.println(`copy area: ${copied.get().area()}`)\n\n      names <- Holder(\"Steve\")\n      stdout.println(`name: ${names.get()}`)","migrationContext":"Java: a generic field of an abstract type is simply left null by a no-arg constructor (type erasure means T() does not exist at all). Kotlin/Swift: generic type parameters have no constructor either; you pass a value or a factory. EK9: T() exists for types that can be constructed, and is checked per parameterisation, so a generic meant for abstract types must not call it - the private no-arg plus the value constructor is the idiom.","keywords":["E06040","E08264","E10030","T()","abstract","constructor","default","generic","inferred","no-arg","parameterise","private"],"primaryTopics":["generic parameterised with abstract type","E10030","private no-arg constructor in a generic"],"typicalErrors":[{"error":"E10030","correct":"default private Holder()","incorrect":"private Holder()\n        item: T()","explanation":"The no-arg constructor calls T(), and the generic is parameterised with the abstract Shape, so T() becomes a constructor call on an abstract type - rejected even though the constructor is private and never called. Leave 'item' unset in the private no-arg instead. See ek9 -h E10030 for details."},{"error":"E08264","correct":"<- rtn as Holder of T: Holder(item)","incorrect":"<- rtn as Holder of T: Holder() of T","explanation":"'Holder() of T' reaches the private no-arg constructor, which leaves 'item' without a value; it may only be reached through this() from a constructor that sets 'item'. Construct through the value constructor. See ek9 -h E08264 for details."}],"companions":[]}
{"id":2060,"category":"Operators and Expressions","question":"Why must I put parentheses around '?:' when I mix it with '+' or '=='?","url":"https://ek9.io/qa/QA2060.html","alternatePhrasings":["What does error E08269 mean?","Does '?:' bind tighter than '+' in EK9?","Why is 'total <- first + second ?: fallback' a compile error?","Can I mix '<?' with arithmetic without brackets?","What is the precedence of the Elvis operator in EK9?"],"answer":"EK9 never lets a coalescing operator - null coalescing '??', coalesce (Elvis) '?:' or the comparison coalescing operators '<?', '<=?', '>?', '>=?' - mix with a different binary operator without parentheses (E08269). '?:' binds tighter than arithmetic, comparison and logic, so 'first + second ?: fallback' would be 'first + (second ?: fallback)': when first is unset the sum is unset and the fallback never applied to it. Kotlin puts '?:' below '+', C# and JavaScript put '??' below '==', so whichever reading you expect, a reader from another language expects the other. The parentheses say which one you mean:\n  sumOrFallback <- (first + second) ?: fallback\n  firstPlusEither <- first + (second ?: fallback)\n\nA chain of the SAME operator reads one way only and needs no parentheses:\n  chained <- first ?: second ?: fallback\n\n'?:' and '??' are different operators, so 'first ?: second ?? fallback' needs parentheses too, and so does 'first ?? second + fallback' - write '(first ?? second) + fallback'.\n\nSee Q1091 for the coalescing operator (Elvis), Q1093 for chained coalescing and Q2061 for the operand types coalescing accepts.","ek9Example":"#!ek9\ndefines module qa.operators.coalescingparentheses\n\n  defines function\n\n    reading()\n      -> start as Integer\n      <- rtn as Integer: start\n\n  defines program\n\n    CoalescingParenthesesDemo()\n      stdout <- Stdout()\n      first <- reading(Integer())\n      second <- reading(5)\n      fallback <- reading(100)\n\n      //The sum is unset because first is unset, so the fallback is taken.\n      sumOrFallback <- (first + second) ?: fallback\n      stdout.println(`(first + second) ?: fallback = ${sumOrFallback}`)\n\n      //Only second is defaulted; first is still unset, so the sum is unset.\n      firstPlusEither <- first + (second ?: fallback)\n      firstSet <- firstPlusEither?\n      stdout.println(`first + (second ?: fallback) is set: ${firstSet}`)\n\n      chained <- first ?: second ?: fallback\n      stdout.println(`first ?: second ?: fallback = ${chained}`)","migrationContext":"Kotlin: 'a + b ?: c' is '(a + b) ?: c' ('?:' is below '+'). C#/JavaScript: 'a + b ?? c' is '(a + b) ?? c', and JavaScript made mixing '??' with '||'/'&&' a SyntaxError. EK9 applies that JavaScript rule to '??', '?:' and the '<?' family against every other binary operator.","keywords":["<?","?:","E08269","coalescing","elvis","operator mixing","parentheses","precedence"],"primaryTopics":["?: coalesce (Elvis)","operator precedence"],"typicalErrors":[{"error":"E08269","correct":"      sumOrFallback <- (first + second) ?: fallback","incorrect":"      sumOrFallback <- first + second ?: fallback","explanation":"'?:' binds tighter than '+', so this would be 'first + (second ?: fallback)'. Parenthesise whichever operator is meant to apply first. See ek9 -h E08269 for details."},{"error":"E08269","correct":"      chained <- first ?: second ?: fallback","incorrect":"      chained <- first ?: second ?? fallback","explanation":"'?:' and '??' are different operators, so mixing them needs parentheses; a chain of the same operator does not. See ek9 -h E08269 for details."}],"companions":[]}
{"id":2061,"category":"Operators and Expressions","question":"What type does 'a ?: b' have when a and b are different types?","url":"https://ek9.io/qa/QA2061.html","alternatePhrasings":["Why is 'count ?: label' a compile error when count is an Integer and label a String?","Can a ternary return an Integer in one branch and a String in the other?","What does E50030 mean on a coalescing operator?","Does EK9 promote Integer to Float in a coalescing expression?","How do I default an Optional with '?:'?"],"answer":"Every operator that returns ONE of its operands - '?:', '??', '<?', '<=?', '>?', '>=?' and the ternary 'condition <- this else that' - gives a type the operands already share:\n1. the same type, or a subtype (including Any when an operand is DECLARED as Any)\n2. one operand promoting ('#^') to the other's type - an Integer and a Float give a Float, and only the operand chosen is promoted\n3. otherwise their nearest common super class or trait\n\nNever an invented Any and never a third type: operands that share nothing but Any are a compile error at the operator (E50030), and so are two types that promote to each other or two unrelated nearest traits. The answer never depends on which operand you write first.\n\nGive the operands one type - convert one explicitly ('$count' is a String):\n  shown <- $count ?: label\n\nAn 'Optional of Integer' is not an Integer, so 'maybeCount ?: 0' is E50030 too; use getOrDefault:\n  chosen <- maybeCount.getOrDefault(0)\n\nA literal on the LEFT of '?:' is always set, so the right would never be used (E08094) - put the value that may be unset on the left:\n  promoted <- count ?: amount\n\nSee Q1091 for the coalescing operator (Elvis) and Q2060 for mixing '?:' with other operators.","ek9Example":"#!ek9\ndefines module qa.operators.coalescingtypes\n\n  defines function\n\n    countOf()\n      -> start as Integer\n      <- rtn as Integer: start\n\n    labelOf()\n      -> start as String\n      <- rtn as String: start\n\n  defines program\n\n    CoalescingTypesDemo()\n      stdout <- Stdout()\n      count <- countOf(Integer())\n      amount <- 2.5\n      label <- labelOf(\"none\")\n\n      //Integer promotes to Float, so the result is a Float.\n      promoted <- count ?: amount\n      stdout.println(`count ?: amount = ${promoted}`)\n\n      shown <- $count ?: label\n      stdout.println(`string of count ?: label = ${shown}`)\n\n      maybeCount <- Optional(countOf(3))\n      chosen <- maybeCount.getOrDefault(0)\n      stdout.println(`maybeCount.getOrDefault(0) = ${chosen}`)","migrationContext":"Kotlin: 'if (c) 1 else \"x\"' and '1 ?: \"x\"' are typed Any (Comparable & Serializable). Java: 'c ? 1 : \"x\"' is an intersection type or Object. EK9 rejects operands with nothing but Any in common, so the result can always be used as the type of its operands without a cast (EK9 has none).","keywords":["?:","??","E08094","E50030","coalescing","common supertype","common type","promotion","ternary"],"primaryTopics":["?: coalesce (Elvis)","type compatibility"],"typicalErrors":[{"error":"E50030","correct":"      shown <- $count ?: label","incorrect":"      shown <- count ?: label","explanation":"An Integer and a String share no type but Any, and the result is one of them. Convert one operand so both have one type. See ek9 -h E50030 for details."},{"error":"E50030","correct":"      chosen <- maybeCount.getOrDefault(0)","incorrect":"      chosen <- maybeCount ?: 0","explanation":"An 'Optional of Integer' is not an Integer, so '?:' has no type to give. Use the Optional's own getOrDefault. See ek9 -h E50030 for details."},{"error":"E08094","correct":"      promoted <- count ?: amount","incorrect":"      promoted <- 7 ?: amount","explanation":"A literal on the left of '?:' is always set, so it is always the result and the right is never used. Put the value that may be unset on the left. See ek9 -h E08094 for details."}],"companions":[]}
{"id":2100,"category":"Classes and OOP","question":"How do I make a deep or selective copy of an object?","url":"https://ek9.io/qa/QA2100.html","alternatePhrasings":["Is 'default operator :=:' a deep copy or a shallow copy?","How do I copy an object so that changing the copy does not change the original?","How do I clone an object in EK9?","How do I copy some fields, share others and leave some out?"],"answer":"THE COPY PRINCIPLE\n- 'default operator :=:' is a SHALLOW copy: after 'target :=: source' each of target's properties refers to the SAME object as source's - exactly what 'b <- a' and passing a parameter do. Change a shared object in place ('points += 2') and both see it.\n- A built-in collection copies at its OWN level: 'list :=: other' gives list its own container holding the same element objects. A value type copies its value: 'year :=: other.year'. Integer(x), String(x) and your own copy constructors make new objects.\n- Anything deeper or SELECTIVE is your code: write 'operator :=:' yourself, or a domain method, and decide per property - duplicate it, share it, or leave it out. No generator can know which, so EK9 does not guess. There is no clone().\n\nThe program below renews a car insurance policy for the next period. The new period's policy:\n- DUPLICATES the per-holder data: each named driver is a new Driver (Driver(driver)), so the new period can add a driver or penalty points without touching last period's record;\n- SHARES the insurer's standard wordings: the same Wording objects, in the new policy's own List ('wordings :=: other.wordings'), so a correction to a wording reaches every policy that uses it;\n- DROPS the endorsements that have expired.\n\nThe copying rules live in a hand-written 'operator :=:'; the renewal rules (next period, expired endorsements) live in 'renewForNextPeriod()'. The original period is printed after the renewal is changed: it is untouched, apart from the wording it shares on purpose.","ek9Example":"#!ek9\ndefines module qa.classes.deeporselectivecopy\n\n  defines class\n\n    <?- Per-holder data: each period has its own -?>\n    Driver\n      name as String: String()\n      points as Integer: Integer()\n\n      default private Driver()\n\n      Driver()\n        ->\n          driverName as String\n          driverPoints as Integer\n        name :=: driverName\n        points :=: driverPoints\n\n      <?- A copy constructor: new objects for every property -?>\n      Driver()\n        -> other as Driver\n        name :=: other.name\n        points :=: other.points\n\n      named() as pure\n        -> driverName as String\n        <- rtn as Boolean: name == driverName\n\n      addPoints()\n        -> morePoints as Integer\n        points += morePoints\n\n      operator $ as pure\n        <- rtn as String: `${name}(${points})`\n\n      default operator ?\n\n    <?- The insurer's standard text: shared by every policy that uses it -?>\n    Wording\n      text as String: String()\n\n      default private Wording()\n\n      Wording()\n        -> wordingText as String\n        text :=: wordingText\n\n      revise()\n        -> revisedText as String\n        text :=: revisedText\n\n      operator $ as pure\n        <- rtn as String: text\n\n      default operator ?\n\n    Endorsement\n      text as String: String()\n      lastPeriod as Integer: Integer()\n\n      default private Endorsement()\n\n      Endorsement()\n        ->\n          endorsementText as String\n          endorsementLastPeriod as Integer\n        text :=: endorsementText\n        lastPeriod :=: endorsementLastPeriod\n\n      coversPeriod() as pure\n        -> periodToCheck as Integer\n        <- rtn as Boolean: periodToCheck <= lastPeriod\n\n      operator $ as pure\n        <- rtn as String: `${text} to ${lastPeriod}`\n\n      default operator ?\n\n    Policy\n      holder as String: String()\n      period as Integer: Integer()\n      drivers as List of Driver: List() of Driver\n      wordings as List of Wording: List() of Wording\n      endorsements as List of Endorsement: List() of Endorsement\n\n      default private Policy()\n\n      Policy()\n        ->\n          policyHolder as String\n          policyPeriod as Integer\n        holder :=: policyHolder\n        period :=: policyPeriod\n\n      addDriver()\n        -> driver as Driver\n        drivers += driver\n\n      addWording()\n        -> wording as Wording\n        wordings += wording\n\n      addEndorsement()\n        -> endorsement as Endorsement\n        endorsements += endorsement\n\n      addPointsTo()\n        ->\n          driverName as String\n          morePoints as Integer\n        for driver in drivers\n          if driver.named(driverName)\n            driver.addPoints(morePoints)\n\n      <?- The next period: a copy of this policy, moved on a period, without what has expired -?>\n      renewForNextPeriod()\n        <- renewed as Policy: Policy()\n        renewed :=: this\n        renewed.startNextPeriod()\n\n      private startNextPeriod()\n        period++\n        stillInForce <- List() of Endorsement\n        for endorsement in endorsements\n          if endorsement.coversPeriod(period)\n            stillInForce += endorsement\n        endorsements: stillInForce\n\n      describe() as pure\n        <- rtn as String: `${period} ${holder} drivers=${drivers} wordings=${wordings} endorsements=${endorsements}`\n\n      <?- A selective copy: duplicate the per-holder data, share the standard wordings -?>\n      operator :=:\n        -> other as Policy\n        holder :=: other.holder\n        period :=: other.period\n        drivers: List() of Driver\n        for driver in other.drivers\n          drivers += Driver(driver)\n        wordings :=: other.wordings\n        endorsements :=: other.endorsements\n\n      default operator ?\n\n  defines program\n\n    RenewPolicy()\n      stdout <- Stdout()\n\n      standardTerms <- Wording(\"Standard terms v1\")\n\n      current <- Policy(\"Pat\", 2026)\n      current.addDriver(Driver(\"Alex\", 3))\n      current.addWording(standardTerms)\n      current.addEndorsement(Endorsement(\"Courtesy car\", 2026))\n      current.addEndorsement(Endorsement(\"Protected bonus\", 2028))\n\n      renewal <- current.renewForNextPeriod()\n      renewal.addDriver(Driver(\"Sam\", 0))\n      renewal.addPointsTo(\"Alex\", 2)\n\n      stdout.println(current.describe())\n      stdout.println(renewal.describe())\n\n      standardTerms.revise(\"Standard terms v2\")\n      stdout.println(current.describe())\n      stdout.println(renewal.describe())","migrationContext":"Java: clone() is shallow and Cloneable is widely considered broken; deep copies are hand-written copy constructors or serialisation tricks. C#: MemberwiseClone() is shallow. Python: copy.copy() is shallow, copy.deepcopy() copies everything reachable (it cannot share or drop selectively). C++: the implicit copy constructor copies members, so a pointer member is shared. EK9: 'default operator :=:' is shallow, a collection copies at its own level, and a deep or selective copy is an 'operator :=:' or domain method you write.","keywords":[":=:","aliasing","clone","copy","copy constructor","deep copy","default operator","operator :=:","renewal","selective copy","shallow copy","shared reference"],"primaryTopics":["operators","classes","copy semantics"],"typicalErrors":[{"error":"E50060","correct":"      renewal <- current.renewForNextPeriod()","incorrect":"      renewal <- current.clone()","explanation":"EK9 has no clone(): no generated method can know which properties to duplicate, share or drop. Write the copy yourself - an 'operator :=:' or a domain method such as renewForNextPeriod(). See ek9 -h E50060 for details."}],"companions":[]}
{"id":2130,"category":"Advanced Type System","question":"Can a dynamic function created in a method capture the class's own private property, and does it share it?","url":"https://ek9.io/qa/QA2130.html","alternatePhrasings":["How does a callback update a class's own field in EK9?","Why is reading a property inside a dynamic function body E06180?","Is '(counter: hits)' a copy or the same object?","How do I let a closure created in a method change the object's tally?"],"answer":"Yes - name the property in the CAPTURE LIST: '(counter: hits) is Notify as function'. The capture list is evaluated in the method that creates the dynamic function, where the class's private properties are visible, so it may name them (a dynamic class or function nested inside a dynamic function may likewise capture that function's own capture). A capture SHARES the object - EK9 has no primitives - so 'counter++' inside the function moves 'hits' itself: the class's own method chose to let the property escape, exactly as a method returning the property would. The BODY of a dynamic function or class cannot name 'hits' directly: a dynamic construct is not inside the object, so that is E06180 and the message names the capture. CHOOSE SHARED (this example) when the dynamic construct is meant to update the object - a callback recording into the object's tally, a listener appending to its log. CHOOSE A COPY, '(count: Integer(hits))', when the dynamic construct only needs a starting value it may then change without moving the property (Q2131). Rebinding the capture with ':=' inside the body never moves the property; only a mutating operator ('++', '+=', ':=:', ':~:', ...) acts on the shared object. A capture holds the OBJECT, not the field: 'hits := Integer(0)' in a method re-points the field and the function keeps the old object, so after 'reset()' a click no longer reaches 'hits' - only a mutating operator is shared.","ek9Example":"#!ek9\ndefines module qa.advancedtypes.capturesharesproperty\n\n  defines function\n\n    Notify() as abstract\n      -> event as String\n\n  defines class\n\n    Tracker\n      hits as Integer: 0\n\n      //SHARED BY DESIGN: the callback updates the tracker's own tally.\n      callback()\n        <- rtn as Notify: (counter: hits) is Notify as function\n          if event == \"click\"\n            counter++\n\n      //Re-points the field: the callback still holds the OLD object.\n      reset()\n        hits := Integer(0)\n\n      hitCount()\n        <- rtn as Integer: Integer(hits)\n\n      default operator ?\n\n  defines program\n\n    CaptureSharesProperty()\n      stdout <- Stdout()\n      tracker <- Tracker()\n      onEvent <- tracker.callback()\n      stdout.println(`hits before: ${tracker.hitCount()}`)\n      for event in [\"click\", \"scroll\", \"click\"]\n        stdout.println(event)\n        onEvent(event)\n      stdout.println(`clicks counted: ${tracker.hitCount()}`)\n      tracker.reset()\n      onEvent(\"click\")\n      stdout.println(`after reset and a click: ${tracker.hitCount()}`)","migrationContext":"Java: a lambda in an instance method reads 'this.hits' implicitly but cannot assign it through a local copy, and an Integer field is immutable, so a shared counter needs an AtomicInteger or an int[] holder. Kotlin: a lambda captures 'this' and may assign the property directly. JavaScript: an arrow function captures 'this'. EK9 makes the escape explicit: the property is named in the capture list, so a reader sees at the creation site exactly which of the object's fields the dynamic construct can reach - and Integer is mutable in place, so '++' on the capture is visible through the property.","keywords":["E06180","callback","capture","capture list","closure","dynamic function","private property","shared"],"primaryTopics":["capture semantics","dynamic functions"],"typicalErrors":[{"error":"E06180","correct":"(counter: hits) is Notify as function\n          if event == \"click\"\n            counter++\n","incorrect":"() is Notify as function\n          if event == \"click\"\n            hits++\n","explanation":"The body of a dynamic function cannot name a property of the object around it - the dynamic function is not inside that object. Capture it in the capture list, '(counter: hits)', which shares the object so 'counter++' moves 'hits'; or capture a copy, '(counter: Integer(hits))', if the property must not move. See ek9 -h E06180 for details."}],"companions":[]}
{"id":2131,"category":"Advanced Type System","question":"How does a dynamic function take a class property as a starting value without changing the property?","url":"https://ek9.io/qa/QA2131.html","alternatePhrasings":["How do I capture a copy of a property into a dynamic function in EK9?","Why does my closure change the class's field, and how do I stop it?","What is '(left: Integer(limit))' in a capture list?","How do I give a closure its own working value from a field?"],"answer":"Capture a COPY made with the type's copy constructor: '(left: Integer(limit))'. The capture list is evaluated in the method that creates the dynamic function, where the class's private properties are visible, and 'Integer(limit)' makes a new Integer there - the dynamic function owns it, so 'left--' moves only its own working value and 'limit' stays where it was. The copy is made once, when the dynamic function is created; a second call of the method starts a fresh one from the property as it is then. Capturing the property itself, '(left: limit)', would SHARE the object and every 'left--' would move 'limit' too (Q2130). CHOOSE A COPY (this example) when the dynamic construct needs an initial value it then works on - a countdown, a cursor, a retry budget. CHOOSE SHARED when the dynamic construct exists to update the object - a callback recording into the object's tally. A built-in copies at its own level (a List copy is a new container holding the same elements); for your own type, its copy constructor or 'operator :=:' decides how deep the copy goes.","ek9Example":"#!ek9\ndefines module qa.advancedtypes.captureinitialvalue\n\n  defines function\n\n    Remaining() as abstract\n      <- rtn as Integer?\n\n  defines class\n\n    Retries\n      limit as Integer: 3\n\n      //AN INITIAL VALUE: the countdown starts from the limit and owns its own copy.\n      countdown()\n        <- rtn as Remaining: (left: Integer(limit)) is Remaining as function\n          left--\n          rtn: Integer(left)\n\n      currentLimit()\n        <- rtn as Integer: Integer(limit)\n\n      default operator ?\n\n  defines program\n\n    CaptureInitialValue()\n      stdout <- Stdout()\n      retries <- Retries()\n      remaining <- retries.countdown()\n      stdout.println(`limit before: ${retries.currentLimit()}`)\n      stdout.println(`remaining: ${remaining()}`)\n      stdout.println(`remaining: ${remaining()}`)\n      stdout.println(`remaining: ${remaining()}`)\n      stdout.println(`limit after: ${retries.currentLimit()}`)\n      fresh <- retries.countdown()\n      stdout.println(`fresh countdown: ${fresh()}`)","migrationContext":"Java: a lambda reads 'this.limit' live, so a snapshot is a local 'int start = limit;' taken before the lambda and copied into an int[] or AtomicInteger to be changed. Kotlin/JavaScript: copy into a local 'var' before the closure. EK9: the copy is written in the capture list itself, so the creation site states which fields are shared and which are starting values.","keywords":["capture","capture list","closure","copy constructor","dynamic function","initial value","private property","snapshot"],"primaryTopics":["capture semantics","dynamic functions"],"typicalErrors":[],"companions":[]}
{"id":2160,"category":"Operators and Expressions","question":"What is EK9's operator precedence - does 'and' bind tighter than 'or', and where does 'not' sit?","url":"https://ek9.io/qa/QA2160.html","alternatePhrasings":["Is 'a or b and c' read as 'a or (b and c)' in EK9?","Does 'not x == y' negate the comparison or x?","Is '^' right-associative in EK9?","What does '-x ^ 2' evaluate to in EK9?","Operator precedence table for EK9","Why is 'a == not b' a compile error in EK9?","Is '-2 ^ 2' equal to 4 or -4 in EK9?","Why is '$ not b' a compile error in EK9?"],"answer":"EK9 follows the convention of C, Java and Python. Tightest first: the postfix operators and the prefixes $ $$ #^ #< #> #? length abs sqrt ~ and 'is empty'; then '^' (right-associative, so 2 ^ 3 ^ 2 is 512); then unary minus (so -x ^ 2 is -(x ^ 2), and -2 ^ 2 is -(2 ^ 2) = -4.0 too - write (-2) ^ 2 to square the negative number); then the one coalescing level ?: ?? <? <=? >? >=?; then * / mod rem; + -; << >>; <=> <~>; then < <= > >= matches contains in and 'in a ... b'; then == <> !=; then word 'not'; then 'and'; then 'xor'; then 'or'; and the ternary last. So 'a or b and c' is 'a or (b and c)', 'name in names == expected' compares the membership, 'valid and age in 18 ... 65' is 'valid and (age in 18 ... 65)', and 'not age == limit' and 'not name in names' negate the whole test, as in Python. '~' applies to its operand alone, so '~name in names' asks a String for '~' (E07620). Word 'not' is never an operand of another operator unless parenthesised, except 'and', 'xor' and 'or' (E08273): read from the right it would NOT reach past an operator before it, so 'ready == not done' is a compile error, as in Python - write 'ready == (not done)', or 'not (ready == done)' - and every prefix operator binds tighter than it, so '$ not done' is an error too - write '$ (not done)'; word 'not', '~' and unary '-' are the same negation, so a negation applied to a negation cancels out and is E08274, in any mix of spellings and parenthesised or not ('not not done', 'not (not done)', '~~done', '~not done', '- -x', '-(-x)') - write 'done', or a single 'not done' (subtraction is not a negation: '-(a - b)' and 'a - -b' are fine); 'ready and not done' needs no parentheses because 'and', 'xor' and 'or' bind below 'not'. A coalescing operator ('??' included) mixed with any other binary operator needs parentheses (E08269): '(first ?? second) + third', never 'first ?? second + third'.","ek9Example":"#!ek9\ndefines module qa.operators.precedence\n\n  defines constant\n    low <- 18\n    high <- 65\n\n  defines function\n\n    flagOf()\n      -> answer as Boolean\n      <- rtn as Boolean: answer\n\n    numberOf()\n      -> amount as Integer\n      <- rtn as Integer: amount\n\n  defines program\n\n    OperatorPrecedenceDemo()\n      stdout <- Stdout()\n      yes <- flagOf(true)\n      no <- flagOf(false)\n\n      //'and' binds tighter than 'or': this is 'yes or (no and no)'.\n      either <- yes or no and no\n      stdout.println(`yes or no and no: ${either}`)\n\n      //'^' associates right: 2 ^ (3 ^ 2).\n      two <- numberOf(2)\n      three <- numberOf(3)\n      power <- two ^ three ^ two\n      stdout.println(`2 ^ 3 ^ 2: ${power}`)\n\n      //Unary minus binds below '^': -(x ^ 2).\n      negated <- -three ^ two\n      stdout.println(`-x ^ 2 with x = 3: ${negated}`)\n\n      //The range test is a comparison: valid and (age in low ... high).\n      age <- numberOf(30)\n      inRange <- yes and age in low ... high\n      stdout.println(`valid and age in low ... high: ${inRange}`)\n\n      //'in' binds tighter than '==': (name in names) == expected.\n      names <- [\"Steve\", \"Stephen\"]\n      name <- \"Steve\"\n      member <- name in names == yes\n      stdout.println(`name in names == expected: ${member}`)\n\n      //Word 'not' binds below the comparisons: not (age == low), not (name in names).\n      notLow <- not age == low\n      stdout.println(`not age == low: ${notLow}`)\n      absent <- not name in names\n      stdout.println(`not name in names: ${absent}`)\n\n      //A negative literal is no exception to unary minus binding below '^': -(2 ^ 2).\n      literal <- -2 ^ 2\n      stdout.println(`-2 ^ 2: ${literal}`)\n\n      //A coalescing operator mixed with another operator is parenthesised (E08269).\n      total <- (three ?? two) + two\n      stdout.println(`(x ?? y) + y: ${total}`)\n\n      //Word 'not' as an operand of '==' is parenthesised (E08273).\n      same <- yes == (not no)\n      stdout.println(`yes == (not no): ${same}`)\n\n      //Word 'not' as the operand of a prefix operator is parenthesised too (E08273).\n      text <- $ (not no)\n      stdout.println(`string of (not no): ${text}`)\n\n      //A negation of a negation, parenthesised or not, cancels out: write the value itself (E08274).\n      plain <- no\n      stdout.println(`no without a double negation: ${plain}`)","migrationContext":"C/Java: '&&' over '||', '!' binds to its operand - EK9's 'not' binds below the comparisons like Python's 'not' and SQL's NOT, and '~' plays the part of '!'. Python: identical for and/or/not and for '**' (right-associative, above unary minus). Java has no power operator; Math.pow(-x, 2) is what EK9 writes as (-x) ^ 2.","keywords":["associativity","exponent","logical and","logical not","logical or","operator order","precedence","xor"],"primaryTopics":["operator precedence","logical operators"],"typicalErrors":[{"error":"E07620","correct":"      absent <- not name in names","incorrect":"      absent <- ~name in names","explanation":"'~' applies to 'name' alone and a String has no '~' operator. Word 'not' binds below the comparisons, so 'not name in names' negates the membership test. See ek9 -h E07620 for details."},{"error":"E08269","correct":"      total <- (three ?? two) + two","incorrect":"      total <- three ?? two + two","explanation":"'??' is on the one coalescing level, which binds tighter than '+', so the unparenthesised form would be '(three ?? two) + two' - and other languages read it the other way. Mixing a coalescing operator with another binary operator needs parentheses. See ek9 -h E08269 for details."},{"error":"E08273","correct":"      same <- yes == (not no)","incorrect":"      same <- yes == not no","explanation":"Word 'not' binds below the comparisons, so on the right of '==' it would negate only 'no' while on the left it negates the whole comparison. Parenthesise the negation, or write 'not (yes == no)'. See ek9 -h E08273 for details."},{"error":"E08273","correct":"      text <- $ (not no)","incorrect":"      text <- $ not no","explanation":"Word 'not' is never an operand of another operator unless parenthesised, except 'and', 'xor' and 'or'. '$' binds tighter than word 'not', so '$ not no' would convert only the negation - the same trap as 'yes == not no' written the other way round. Parenthesise the negation: '$ (not no)'. See ek9 -h E08273 for details."},{"error":"E08274","correct":"      plain <- no","incorrect":"      plain <- not not no","explanation":"Word 'not', '~' and unary '-' are the same negation, so two of them cancel out and 'not not no' is just 'no'. EK9 cannot know whether the value or a single negation was meant, so it does not choose: write 'no', or a single 'not no'. See ek9 -h E08274 for details."},{"error":"E08274","correct":"      plain <- no","incorrect":"      plain <- not (not no)","explanation":"Parentheses group, they do not change what is negated: 'not (not no)' is still a negation of a negation, and still just 'no'. Write the value, or a single 'not no'. See ek9 -h E08274 for details."}],"companions":[]}
{"id":2180,"category":"Classes and OOP","question":"Why can't a property initialiser use 'this', not even 'this.width'?","url":"https://ek9.io/qa/QA2180.html","alternatePhrasings":["Why is 'this.width' in a property initialiser E08061?","Can I use this in a property initialiser in EK9?","Can a property initialiser capture 'this' in a dynamic function?","What is two-phase construction in EK9?","How does a property initialiser read another property of the same object?"],"answer":"A property initialiser may not use 'this' at all - not as a value, not in a capture list, and not to reach a property ('this.width'). Write the property's plain name instead: 'height as Integer: width * 2'.\n\nWHY: TWO-PHASE CONSTRUCTION\nBuilding an object has two phases. Phase one gives every property a value: the super constructor runs, then this type's property initialisers in declaration order, then the constructor body. Phase two begins only when EVERY property has a value - the object is complete - and only then may anything use it: call its methods, hand it out, let something else hold it. A property initialiser always runs in phase one, before the constructor body, so the object is NEVER complete while an initialiser runs. 'this' there is a half-built object: a method called through it, or a dynamic function holding it, could reach a property that has not been given its value yet (in Java that compiles and fails with a NullPointerException, or sees a 0 that is not the real value).\n\nWHAT AN INITIALISER MAY READ\nOnly a property declared EARLIER that has its own initialiser - that initialiser has already run (anything else is E08060). Read it by its plain name: 'height as Integer: width * 2'. A capture list in an initialiser is evaluated by the initialiser, so it follows the same rule: '(factor: width)' captures the earlier property's object, while '(owner: this)' would capture the half-built object (E08061). A value that needs more comes from a function ('depth as Integer: defaultDepth()') or from a constructor argument ('label: name' in the constructor).\n\nWHY NOT ALLOW 'this.width', WHICH READS THE SAME FIELD?\nBecause one rule with no exceptions is easier to see and to keep - for people and for AI - than a list of allowed uses. Once 'this' may appear in an initialiser, the next edit is 'this.describe()', '$this' or '(owner: this)', each of which uses the half-built object. So the rule is simply: no 'this' in an initialiser, and E08061 tells you to write 'width'.\n\nWhen the object needs a reference to itself - a child that points back to its parent - build it in the CONSTRUCTOR, where EK9 allows it in one exact shape: see Q2181. For calling the object's own methods during construction see Q1396; for the order initialisers run in see Q1397.","ek9Example":"#!ek9\ndefines module qa.oop.no.this.in.initialiser\n\n  defines function\n\n    Scaler() as abstract\n      -> amount as Integer\n      <- rtn as Integer?\n\n    defaultDepth()\n      <- rtn as Integer: 4\n\n  defines class\n\n    Box\n      width as Integer: 3\n      height as Integer: width * 2\n      depth as Integer: defaultDepth()\n      scale as Scaler: (factor: width) is Scaler as function (rtn: amount * factor)\n      label as String?\n\n      default private Box()\n\n      Box()\n        -> name as String\n        label: name\n\n      width() as pure\n        <- rtn as Integer: width\n\n      volume()\n        <- rtn as Integer: width * height * depth\n\n      scaled()\n        -> amount as Integer\n        <- rtn as Integer: scale(amount)\n\n      describe()\n        <- rtn as String: `${label}: ${width} x ${height} x ${depth}`\n\n      default operator ?\n\n  defines program\n\n    NoThisInInitialiser()\n      stdout <- Stdout()\n      box <- Box(\"crate\")\n      stdout.println(box.describe())\n      stdout.println(`volume: ${box.volume()}`)\n      stdout.println(`scaled 10: ${box.scaled(10)}`)\n      stdout.println(`width: ${box.width()}`)","migrationContext":"Java: a field initialiser may use 'this' freely - pass it out, call overridable methods - and the bugs show up at run time as nulls or zeros. Kotlin: property initialisers may reference 'this'; leaking it is only a warning. Swift: a stored property's default value cannot reference 'self' at all (a compile error) - EK9 takes the same position and adds that the constructor's use of 'this' waits until every property is set. C#: a field initializer cannot use 'this' or read another instance field at all; EK9 is a little more lenient - an initialiser may read an EARLIER initialised property by its plain name - but never through 'this'.","keywords":["E08060","E08061","capture","construction","constructor","field","half-built","initialiser","initializer","property","two-phase"],"primaryTopics":["classes","constructors","E08061"],"typicalErrors":[{"error":"E08061","correct":"height as Integer: width * 2","incorrect":"height as Integer: this.width * 2","explanation":"A property initialiser runs before the constructor, while the object is still being built, so it may not use 'this' at all - even to reach a property. Write the property's plain name: 'width * 2' reads the earlier, already-initialised 'width'."},{"error":"E08061","correct":"(factor: width) is Scaler as function (rtn: amount * factor)","incorrect":"(owner: this) is Scaler as function (rtn: amount * owner.width())","explanation":"The capture list is evaluated by the initialiser, so '(owner: this)' would hand the half-built object to the function. Capture the earlier property itself, '(factor: width)', or build the function in the constructor once the object is complete."}],"companions":[]}
{"id":2181,"category":"Classes and OOP","question":"How does a child object hold a reference back to its parent when the parent creates it in its constructor?","url":"https://ek9.io/qa/QA2181.html","alternatePhrasings":["How do I give a child object a parent reference in EK9?","How do I create a back reference from a child to its owner in a constructor?","Can I use this in a constructor to build a child that points back to the parent?","Is capturing 'this' in a dynamic class inside a constructor leaking this?","Why is '(owner: this)' in my constructor E08061 until the other properties are set?"],"answer":"Use the PARENT LINK. Declare the child property unset ('section as Part?'). In the constructor, set every OTHER property first, then assign the child a dynamic class (or dynamic function) whose capture list captures 'this':\n\n  Document()\n    -> text as String\n    title: text\n    section :=? (owner: this) with trait of Part as class\n      override describe() as pure\n        <- rtn as String: `a section of '${owner.title()}'`\n      default operator ?\n\nThe object is complete as soon as that assignment finishes; from then on the child may be used freely, and it holds the parent object itself, not a copy - after 'doc.retitle(\"Handbook\")' the section describes the new title.\n\nWHY 'this' IS ALLOWED HERE WHEN IT IS NOT ELSEWHERE\nEK9 builds objects in two phases: first every property is given a value, then the object may be used (Q2180, Q1396). Using an incomplete object is forbidden because something might READ a property that has no value yet. Capturing 'this' into a NEW child that is stored in one of the parent's OWN properties reads nothing: none of the child's methods has run, and nothing outside the object holds it - the object only holds itself, exactly as 'me: this' does (Q1471). So EK9 treats that one statement as a store, not a use. It is the only leniency in the rule, and it covers only this shape: a dynamic class or function capturing 'this' that is the whole right-hand side of ':=?', ':=' or ':' to one of the type's own properties ('this.section :=? ...' works as the target too).\n\nTHE CATCH-22 IT SOLVES\nThe child needs the parent, and the parent is not complete until it has the child. Without the leniency neither could go first; with it, the child is created by the very statement that completes the parent.\n\nTHE ONE CONDITION\nEvery other property must already be set on that path through the constructor - or be the target of a LATER capture-assignment, which is how two children work (Q2182). Here 'title' is set first. If 'title' were set after 'section', the object would stay incomplete beyond this one statement, the child would have been handed a parent with no title, and E08061 says so: \"'this' is captured into 'section' before 'title' is set\".\n\nWHAT STAYS FORBIDDEN\nUntil the object is complete the property holding the child IS the object under another name, so calling it, passing it or capturing it again too early is E08061; a dynamic class created this way may not use its owner in its own property initialisers (they run during the capturing statement); and an open or abstract type never hands out 'this' from its constructor (all three: Q2183). Passing the child to another object's method from the constructor publishes the parent, just as passing 'this' does (E08250).","ek9Example":"#!ek9\ndefines module qa.oop.parent.link\n\n  defines trait\n\n    Part\n      describe() as pure\n        <- rtn as String?\n\n  defines class\n\n    Document\n      title as String?\n      section as Part?\n\n      default private Document()\n\n      Document()\n        -> text as String\n        title: text\n        section :=? (owner: this) with trait of Part as class\n          override describe() as pure\n            <- rtn as String: `a section of '${owner.title()}'`\n          default operator ?\n\n      title() as pure\n        <- rtn as String: title\n\n      retitle()\n        -> text as String\n        title: text\n\n      section()\n        <- rtn as Part: section\n\n      default operator ?\n\n  defines program\n\n    ParentLink()\n      stdout <- Stdout()\n      doc <- Document(\"Guide\")\n      part <- doc.section()\n      stdout.println(part.describe())\n\n      //The section holds the document itself, not a copy: it sees the new title.\n      doc.retitle(\"Handbook\")\n      stdout.println(part.describe())","migrationContext":"Java: 'this.section = new Section(this);' compiles anywhere in the constructor, and the Section may call back into a parent whose later fields are still null. Kotlin: the same, with a 'Leaking this' warning. Swift: 'self' cannot be passed anywhere until every stored property is initialised, so a parent link is declared as an optional or implicitly unwrapped property and assigned after phase one - EK9 allows the child to be created as the LAST step of phase one, and only through a capture stored in the parent's own property. Rust: a back reference needs Rc/Weak. EK9: the compiler proves no half-built parent is ever read.","keywords":["E08061","back reference","capture","child","constructor","dynamic class","leaking this","owner","parent link","parent reference","two-phase"],"primaryTopics":["classes","constructors","E08061"],"typicalErrors":[{"error":"E08061","correct":"title: text\n        section :=? (owner: this) with trait of Part as class\n          override describe() as pure\n            <- rtn as String: `a section of '${owner.title()}'`\n          default operator ?","incorrect":"section :=? (owner: this) with trait of Part as class\n          override describe() as pure\n            <- rtn as String: `a section of '${owner.title()}'`\n          default operator ?\n        title: text","explanation":"Capturing 'this' into an own property is allowed only when every other property is already set (or is itself set by a later capture). Here 'title' is still unset, so the section would be handed a parent with no title. Set 'title' first, then create the section."}],"companions":[]}
{"id":2182,"category":"Classes and OOP","question":"How does a constructor give two children (and a callback) a back reference to the parent, and when may it use them?","url":"https://ek9.io/qa/QA2182.html","alternatePhrasings":["How do I create two child objects that both reference their parent in the constructor?","When can a constructor call a child that holds a parent reference?","Can a callback registered in the constructor capture this?","Why is calling the first child before the second is created E08061?"],"answer":"Give each child its own capture-assignment of 'this', one after another, and use none of them until the last one is set:\n\n  name: text\n  lead :=? (team: this, role: \"lead\") with trait of Member as class ...\n  deputy :=? (team: this, role: \"deputy\") with trait of Member as class ...\n  announce :=? (team: this) is Announce as function (rtn: team.rollCall())\n  Stdout().println(`built: ${lead.introduce()}`)\n\nTHE RULE FOR MORE THAN ONE CHILD\nA capture-assignment of 'this' (the parent link, Q2181) is allowed when every OTHER property is already set OR is itself the target of a LATER capture-assignment. So 'lead' may be created while 'deputy' and 'announce' are still unset, because both are about to be assigned the same way. The object becomes complete when the LAST of them ('announce') is set, and only from then on may the constructor call a child.\n\nWHY THE ORDER MATTERS\nA child holds the parent itself, and its methods may call back into the parent. 'lead.introduce()' reads 'team.name()' - harmless - but the parent's 'rollCall()' reads 'deputy', and a callback such as 'announce' runs 'rollCall()'. If the constructor called a child between the two assignments, that call could reach 'deputy' before it has a value: in Java that is a NullPointerException at run time. EK9 does not try to work out which calls happen to be safe; until the object is complete a property holding a capture of 'this' IS the object, so any call, pass or re-capture of it is E08061, and the fix is always the same - move it after the last child is set.\n\nA DYNAMIC FUNCTION IS A CHILD TOO\n'announce :=? (team: this) is Announce as function ...' is the same shape: creating it reads nothing, and its body runs only when it is called - calling it is the use, and that waits for completion. The order of the children does not matter (for example 'announce' could come first); what matters is that nothing is used before the last one is set.\n\nSee Q2183 for the mistakes this rule rejects, and Q1471 for an object holding itself directly ('me: this').","ek9Example":"#!ek9\ndefines module qa.oop.parent.link.two.children\n\n  defines function\n\n    Announce() as abstract\n      <- rtn as String?\n\n  defines trait\n\n    Member\n      introduce() as pure\n        <- rtn as String?\n\n  defines class\n\n    Team\n      name as String?\n      lead as Member?\n      deputy as Member?\n      announce as Announce?\n\n      default private Team()\n\n      Team()\n        -> text as String\n        name: text\n        lead :=? (team: this, role: \"lead\") with trait of Member as class\n          override introduce() as pure\n            <- rtn as String: `${role} of ${team.name()}`\n          default operator ?\n        deputy :=? (team: this, role: \"deputy\") with trait of Member as class\n          override introduce() as pure\n            <- rtn as String: `${role} of ${team.name()}`\n          default operator ?\n        announce :=? (team: this) is Announce as function (rtn: team.rollCall())\n        Stdout().println(`built: ${lead.introduce()}`)\n\n      name() as pure\n        <- rtn as String: name\n\n      rollCall() as pure\n        <- rtn as String: `${lead.introduce()}, ${deputy.introduce()}`\n\n      announceNow()\n        <- rtn as String: announce()\n\n      default operator ?\n\n  defines program\n\n    TwoChildren()\n      stdout <- Stdout()\n      team <- Team(\"Rovers\")\n      stdout.println(team.announceNow())","migrationContext":"Java: children created with 'new Child(this)' in any order, and a child called halfway through the constructor can see a parent whose later fields are null; nothing checks. Kotlin: the same, with 'Leaking this' warnings. Swift: 'self' is unavailable until every stored property is set, so each child link is an optional assigned after phase one. EK9: every child is created as part of phase one, in order, and the compiler proves none is used before the last is in place.","keywords":["E08061","back reference","callback","callback registered in constructor","capture","child","constructor","dynamic class","dynamic function","parent reference","two children"],"primaryTopics":["classes","constructors","E08061"],"typicalErrors":[{"error":"E08061","correct":"deputy :=? (team: this, role: \"deputy\") with trait of Member as class\n          override introduce() as pure\n            <- rtn as String: `${role} of ${team.name()}`\n          default operator ?\n        announce :=? (team: this) is Announce as function (rtn: team.rollCall())\n        Stdout().println(`built: ${lead.introduce()}`)","incorrect":"Stdout().println(`built: ${lead.introduce()}`)\n        deputy :=? (team: this, role: \"deputy\") with trait of Member as class\n          override introduce() as pure\n            <- rtn as String: `${role} of ${team.name()}`\n          default operator ?\n        announce :=? (team: this) is Announce as function (rtn: team.rollCall())","explanation":"'lead' holds the team, and the team is not complete until 'deputy' and 'announce' are set - a child's method may call back into the parent and reach them. Use a child only after the last capture-assignment."}],"companions":[]}
{"id":2183,"category":"Classes and OOP","question":"What mistakes does EK9 reject when a constructor hands 'this' to a child object or callback?","url":"https://ek9.io/qa/QA2183.html","alternatePhrasings":["Why is calling a callback registered in the constructor E08061?","Why can't a dynamic class's property initialiser use the owner it captured?","Why can't an open class capture this in its constructor?","What counts as leaking this from an EK9 constructor?","Where can I use this in a constructor in EK9?"],"answer":"The parent link (Q2181) lets a constructor give one of its own properties a dynamic class or function that captures 'this', while the object is still incomplete. Everything else that would use the half-built object is still rejected - each with E08061, and each for the same reason: a property might be read before it has a value.\n\n1. CALLING A CHILD OR CALLBACK BEFORE THE OBJECT IS COMPLETE\n'onOpen()' runs 'folder.summary()', which reads 'index'. Called before 'index' is set, it would read nothing. Until the last capture-assignment the property holding the capture IS the object under another name, so calling it, passing it (even to a pure function) or capturing it again is E08061. Fix: move the call after the last child is set (this example prints 'opened' at the end of the constructor).\n\n2. USING THE OWNER IN THE DYNAMIC CLASS'S PROPERTY INITIALISERS\nA dynamic class runs its property initialisers when it is created - DURING the capturing statement, while 'index' itself is still being set - so 'prefix as String: `index of ${folder.name()}`' would call into an incomplete object. Its METHODS run only when called, after construction, so they may use the owner freely. Fix: give the initialiser a value that does not need the owner ('prefix as String: \"index of\"') and use the owner in a method.\n\n3. AN OPEN OR ABSTRACT TYPE\nA subtype's own properties are set only AFTER this constructor returns, so in an open or abstract type the object is never complete inside its own constructor: a child created there could call a method the subtype overrides and read the subtype's unset properties. EK9 is strict here - an open or abstract type never hands out 'this' from a constructor, not even to its own property. Fix: keep the type closed (the default), or create the child later, in a method, once construction is over.\n\nTHE OTHER RULES, FOR COMPLETENESS\n- A capture of 'this' while another property is still unset (and not set by a later capture): E08061 (Q2181).\n- 'this' anywhere in a property initialiser, even 'this.width': E08061 (Q2180).\n- 'this' in the arguments of this(...) or super(...): nothing has been constructed yet, E08061.\n- Passing the child to another object's method in the constructor publishes the parent, exactly as passing 'this' does: E08250.\n\nWHY BE THIS STRICT\nEach of these compiles in Java and fails at run time only on the path that happens to read the missing value - often a rare one. EK9 allows exactly one shape (a capture of 'this' stored in the object's own property, as the last step of phase one) and rejects every other use until the object is complete, so a half-built object can never be observed.","ek9Example":"#!ek9\ndefines module qa.oop.parent.link.mistakes\n\n  defines function\n\n    Opened() as abstract\n      <- rtn as String?\n\n  defines trait\n\n    Entry\n      describe() as pure\n        <- rtn as String?\n\n  defines class\n\n    Folder\n      name as String?\n      onOpen as Opened?\n      index as Entry?\n\n      default private Folder()\n\n      Folder()\n        -> text as String\n        name: text\n        onOpen :=? (folder: this) is Opened as function (rtn: folder.summary())\n        index :=? (folder: this) with trait of Entry as class\n          prefix as String: \"index of\"\n          override describe() as pure\n            <- rtn as String: `${prefix} ${folder.name()}`\n          default operator ?\n        Stdout().println(`opened: ${onOpen()}`)\n\n      name() as pure\n        <- rtn as String: name\n\n      summary() as pure\n        <- rtn as String: `${name} with ${index.describe()}`\n\n      index()\n        <- rtn as Entry: index\n\n      default operator ?\n\n  defines program\n\n    ParentLinkMistakes()\n      stdout <- Stdout()\n      folder <- Folder(\"docs\")\n      stdout.println(folder.index().describe())","migrationContext":"Java: listeners registered in a constructor ('button.addListener(this)'), callbacks fired from it, overridable calls and initialisers that use 'this' all compile; 'leaking this' is a well-known source of race conditions and nulls that nothing checks. Kotlin: 'Leaking this in constructor of non-final class' is a warning. Swift: a compile error to use 'self' before phase one ends. C++: virtual calls from a constructor dispatch to the base version, silently. EK9: E08061 for every use of the half-built object, E08250 for publishing it.","keywords":["E08061","E08250","abstract","back reference","callback registered in constructor","constructor","dynamic class","initialiser","leaking this","open","parent reference"],"primaryTopics":["classes","constructors","E08061"],"typicalErrors":[{"error":"E08061","correct":"index :=? (folder: this) with trait of Entry as class\n          prefix as String: \"index of\"\n          override describe() as pure\n            <- rtn as String: `${prefix} ${folder.name()}`\n          default operator ?\n        Stdout().println(`opened: ${onOpen()}`)","incorrect":"Stdout().println(`opened: ${onOpen()}`)\n        index :=? (folder: this) with trait of Entry as class\n          prefix as String: \"index of\"\n          override describe() as pure\n            <- rtn as String: `${prefix} ${folder.name()}`\n          default operator ?","explanation":"'onOpen' holds the folder, and the folder is not complete until 'index' is set - calling it runs summary(), which reads 'index'. Call a child or callback only after the last capture-assignment."},{"error":"E08061","correct":"prefix as String: \"index of\"","incorrect":"prefix as String: `index of ${folder.name()}`","explanation":"A dynamic class's property initialisers run while it is being created - during the statement that is still setting 'index' - so the captured folder is incomplete there. Use the owner in a method, which runs only when called."},{"error":"E08061","correct":"    Folder\n      name as String?","incorrect":"    Folder as open\n      name as String?","explanation":"A subtype's properties are set only after this constructor returns, so an open or abstract type never hands out 'this' from its constructor - not even to its own property. Keep the type closed, or create the child in a method after construction."}],"companions":[]}
{"id":2237,"category":"Classes and OOP","question":"Can an EK9 record have no fields?","url":"https://ek9.io/qa/QA2237.html","alternatePhrasings":["Why does EK9 reject an empty record?","Can I use an empty record as a marker type in EK9?","What does E07237 mean for a record?","Can an abstract record have no fields in EK9?"],"answer":"Only if it is abstract. A record exists only to carry data - it cannot have methods (E07290) - so a CONCRETE record with no fields, counting the ones it inherits, would hold nothing and do nothing: that is E07237. Two field-less shapes stay legal. An ABSTRACT record may have no fields: it is the root of a record hierarchy, the polymorphic type its sub-records share (Measurement below). And a sub-record may add no fields of its own when it INHERITS some (Altitude below inherits 'metres' from Length). For anything else, give the record the field it carries; if it was meant to carry behaviour, make it a class or component (a class or component with no fields is set simply by being present); if it was meant as a marker of one of a fixed set of values, use an enumeration. Every concrete record therefore has a field, which is also why every concrete record defines what 'set' means with its own '?' (E07235).","ek9Example":"#!ek9\ndefines module qa.records.need.fields\n\n  defines record\n\n    //A concrete record: it carries one field.\n    Reading\n      celsius <- 21.5\n      default operator\n\n    //The root of a record hierarchy may have no fields: it is abstract.\n    Measurement as abstract\n      operator $ as pure abstract\n        <- rtn as String?\n      default operator ?\n\n    Length extends Measurement as open\n      metres as Float: Float()\n\n      Length() as pure\n        -> distance as Float\n        metres :=? distance\n\n      default operator $\n      default operator ?\n\n    //Adds no field of its own, but inherits 'metres'.\n    Altitude extends Length\n      Altitude() as pure\n        -> height as Float\n        super(height)\n\n      default operator $\n\n  defines function\n\n    describe()\n      -> measurement as Measurement\n      <- rtn as String: $measurement\n\n  defines program\n\n    RecordsNeedFields()\n      stdout <- Stdout()\n      reading <- Reading()\n      stdout.println($reading)\n      stdout.println(describe(Length(3.0)))\n      stdout.println(describe(Altitude(8848.0)))","migrationContext":"Java: an empty record 'record Marker() {}' compiles, and is used as a marker or tag. Kotlin: a data class needs at least one primary constructor parameter - the same rule as EK9, without EK9's abstract-root exception (Kotlin uses a sealed interface for that). Python: an empty @dataclass is legal. EK9 follows Kotlin: a record is data, so a concrete one must carry some; a polymorphic root is an abstract record (or a trait).","keywords":["abstract record","data class","empty record","field-less","marker type","no fields","record","record hierarchy"],"primaryTopics":["record","record fields"],"typicalErrors":[{"error":"E07237","correct":"    Reading\n      celsius <- 21.5\n","incorrect":"    Reading\n","explanation":"With its only field removed, the concrete record 'Reading' carries no data, and a record cannot have methods either, so it is pointless. Give it a field, make it a class or component if it carries behaviour, or mark it 'as abstract' if it is the root of a record hierarchy. See ek9 -h E07237 for details."}],"companions":[]}
