Question: What does a minimal EK9 program look like?

Hello World in EK9
Simplest EK9 program
EK9 getting started example

Answer:
A minimal EK9 program requires three things:
1. A module declaration (`defines module`)
2. A program construct (`defines program`)
3. A program entry point (a named function inside `defines program`)

The program name becomes the entry point. EK9 uses indentation (2 spaces) to define scope — no braces needed.

To compile and run:
  ek9 -c hello.ek9    (compile)
  ek9 hello.ek9       (run)

For the full Stdout API, run: ek9 -h Stdout

See 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.

defines module qa.getting.started.minimal

  defines program
    MinimalProgram()
      stdout <- Stdout()
      stdout.println("Hello, World!")

basic beginner first hello intro minimal program simple start world

hello world
minimal example
first ek9
getting started

defines program
    MinimalProgram()
      stdout <- Stdout()
      stdout.println("Hello, World!")
    MinimalProgram()
      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.

Oracle can generate a program construct with correct module wrapping, entry point, and Stdout setup.


Question: How do I compile and run an EK9 program?

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:
  chmod u+x hello.ek9
  ./hello.ek9
This auto-compiles and runs in one step.

Alternatively, use the ek9 command explicitly:
  ek9 hello.ek9           compile and run
  ek9 -c hello.ek9        compile only (incremental)
  ek9 -C hello.ek9        full recompile from scratch

If a file contains multiple programs, specify which to run:
  ek9 -r ProgramName hello.ek9

Compilation variants:
  ek9 -cg hello.ek9       compile with debug information
  ek9 -cd hello.ek9       compile with dev code and debug info

Additional modes:
  ek9 -d 5005 hello.ek9   run with debugger on port 5005
  ek9 -t hello.ek9        run tests from dev/ directory
  ek9 -E3 hello.ek9       rich error messages with fix suggestions

For the full list of CLI options, run: ek9 -h

See Q1 for a minimal program. See Q12 for installation. See Q21 for the built-in help system.

defines module qa.getting.started.compile.and.run

  defines program
    CompileAndRun()
      stdout <- Stdout()
      stdout.println("Program compiled and running!")

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.

beginner build cli command compile debug execute first flags incremental intro program recompile run start

compile
run program
execute
build and run

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.


Question: How do I print output to the console?

How to print text in EK9
EK9 equivalent of println
How to display output in EK9

Answer:
Use the built-in Stdout type:
  stdout <- Stdout()
  stdout.println("Hello, World!")

println() adds a newline, print() does not:
  stdout.print("no newline")
  stdout.println(" with newline")

String interpolation uses backticks with ${}:
  name <- "Steve"
  stdout.println(`Hello ${name}`)

Concatenation uses the + operator:
  stdout.println("Value is [" + name + "]")

Any type with a $ (string) operator can be printed directly.

For error/diagnostic output, use Stderr instead:
  stderr <- Stderr()
  stderr.println("Something went wrong")

In stream pipelines, stdout is a sink:
  cat items > stdout

For the full Stdout API, run: ek9 -h Stdout

See Q4 for reading input. See Q37 for string interpolation and methods. See Q43 for escaping in interpolated strings.

defines module qa.getting.started.print.output

  defines program
    PrintOutput()
      stdout <- Stdout()

      stdout.println("Hello, World!")
      stdout.print("no ")
      stdout.println("newline needed")

      name <- "EK9"
      stdout.println(`Welcome to ${name}`)
      stdout.println(`Language: [${name}]`)

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.

beginner console display first intro output print println screen start stdout text write

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.


Question: How do I read input from the user?

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:
  stdin <- Stdin()

Read a single line with a guard (only executes if input is available):
  if line <- stdin.next()
    stdout.println(`You entered: ${line}`)

Iterate over all lines until EOF:
  while stdin?
    stdout.println(stdin.next())

In a stream pipeline, stdin is a source:
  cat stdin | filter by notEmpty > stdout

Stdin implements the StringInput trait with these methods:
  hasNext()    check if more input is available
  next()       get the next line as String
  ?            isSet operator, true when input available

Stdin works with both interactive input and piped data:
  echo "Hello" | ek9 myProgram.ek9
  cat data.txt | ./myProgram.ek9
  ls -la | ek9 myProgram.ek9
The same program handles all three cases without any changes.

When piped input ends or the user sends EOF (Ctrl+D on Unix, Ctrl+Z on Windows), hasNext() returns false.

For the full Stdin API, run: ek9 -h Stdin

See Q3 for printing output. See Q37 for string operations on input lines.

defines module qa.getting.started.read.input

  defines program
    ReadInput()
      stdin <- Stdin()
      stdout <- Stdout()

      stdout.println("Type something and press Enter:")
      if line <- stdin.next()
        stdout.println(`You said: ${line}`)

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.

beginner console first input interactive intro keyboard pipe read readline start stdin user

read input
user input
stdin
keyboard input

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.

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.


Question: How are EK9 source files structured?

What is the structure of an EK9 file?
How do I organize an EK9 source file?
What sections does an EK9 file have?

Answer:
Every EK9 source file follows this structure:

1. Shebang line: #!ek9 (required first line, enables direct execution on Unix)
2. Optional comment header between doc comment markers
3. Module declaration: `defines module <dotted.name>`
4. Optional `references` block (imports from other modules)
5. One or more construct blocks inside the module

Idiomatic ordering of construct blocks (top to bottom):
  defines type        type aliases and constrained types
  defines constant    named constant values
  defines record      data-only types (fields, constructors, operators)
  defines trait       interfaces for polymorphism
  defines class       full object types with methods and operators
  defines function    standalone functions
  defines component   dependency-injected types
  defines text        internationalized text templates
  defines service     web service endpoints
  defines application wires components together
  defines program     executable entry points (always last)

Within aggregates (classes, records, traits) the ordering is enforced by the grammar:
  1. Properties/fields
  2. Constructors and methods
  3. Operators
  4. default operator (generates synthetic operators like ==, <>, $, #?)

Style guidelines:
- Use only ONE `defines <construct>` block per type per file (avoid scattered blocks)
- Multiple files CAN share the same module name, splitting a module across files by concern
- Directory structure is your choice (unlike Java, the module name does not dictate the path)

One module per file. The module name is the namespace for everything defined in it.

For module organization, run: ek9 -q organize code modules

See 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.

defines module qa.getting.started.source.file.structure

  defines constant
    Greeting <- "Hello from EK9"

  defines function
    decorateMessage() as pure
      -> message as String
      <- result as String: `[${message}]`

  defines record
    Person
      name as String: String()

      Person()
        -> n as String
        name :=: n

      default operator ?

      operator $ as pure
        <- rtn as String: name

  defines program
    ShowStructure()
      stdout <- Stdout()
      person <- Person("Steve")
      decorated <- decorateMessage($person)
      stdout.println(Greeting)
      stdout.println(decorated)

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.

beginner construct defines file first intro layout migrate module ordering organize section source start structure

source file structure
file structure
program structure

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.

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.

Person() as pure
      -> message as String
      <- 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.


Question: How do I organize code into modules?

How do I access code from a different module?

Answer:
Every EK9 source file declares exactly one module with `defines module <dotted.name>`.

Three key concepts:

1. Same module, multiple files:
   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.

2. Cross-module access with `references`:
   To use a symbol from another module, add a `references` block after the module declaration:
     references
       other.module::SomeType
       other.module::someFunction
   Then use `SomeType` and `someFunction` directly in your code.

3. Fully qualified names (no import):
   You can skip `references` and use the full path inline:
     result <- other.module::someFunction(value)

Key rules:
- Module names must be lowercase with dot separators
- References must list each symbol explicitly (no wildcards)
- `::` separates the module name from the symbol name
- References must appear before the first construct block
- References must be in alphabetical order (E11026 if not)
- Module hierarchy is flat: `com.foo.bar` has no special access to `com.foo`
- Directory structure is your choice (unlike Java, module name does not dictate path)
- One module per file (each file declares exactly one module)

The deliberate absence of wildcards forces you to think about coupling. If your references list is getting long, it may be time to refactor.

The code example below shows a well-organized module with constants, functions, records, and a program all working together within a single module.

See Q5 for source file structure. See Q7 for managing dependencies. See Q13 for the difference between packages and modules.

defines module qa.getting.started.organize.modules

  defines constant
    PI <- 3.142

  defines function
    areaOfCircle() as pure
      -> diameter as Float
      <- area as Float: PI * (diameter / 2.0) ^ 2

  defines record
    Circle
      diameter as Float: Float()

      Circle()
        -> d as Float
        diameter :=: d

      default operator ?

      operator $ as pure
        <- rtn as String: `Circle(diameter=${diameter})`

  defines program
    OrganizeModules()
      stdout <- Stdout()

      circle <- Circle(10.0)
      area <- areaOfCircle(circle.diameter)

      stdout.println("Module organization example")
      stdout.println($circle)
      stdout.println(`Area: ${area}`)

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.

beginner cross-module files first import intro migrate module namespace organize package references split start structure

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.

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.


Question: How do I manage dependencies 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`.

No separate build file (pom.xml, Cargo.toml, go.mod) is needed. Everything lives in your .ek9 source file.

Package metadata:
  version as Version: 1.0.0-0          semantic version with build number
  description as String = "..."         what the package does
  license <- "MIT"                      license identifier
  tags <- [ "tools", "networking" ]     categorization tags
  publicAccess <- true                  visible to other packages

Dependencies use Dict literals:
  deps <- {
    "ekopen.network.utils": "1.6.1-9"
  }

Dev-only dependencies (not included in production):
  devDeps <- {
    "ekopen.test.helpers": "2.0.0-0"
  }

Exclude transitive dependencies:
  excludeDeps <- {
    "unwanted.transitive.dep": "ekopen.network.utils"
  }

CLI commands for the dependency lifecycle:
  ek9 -Dp app.ek9             resolve dependencies (cleans first)
  ek9 -Dp -v app.ek9          resolve and show accepted/rejected deps
  ek9 -I app.ek9              install to local library cache
  ek9 -P app.ek9              package for deployment (uses -O3)
  ek9 -D app.ek9              deploy (triggers -P and -Gk if needed)
  ek9 -Gk app.ek9             generate signing keys

Version management:
  ek9 -IV minor app.ek9       increment minor version
  ek9 -SV 2.0.0 app.ek9       set version explicitly
  ek9 -PV app.ek9             print current version

Version format: MAJOR.MINOR.PATCH-BUILD (e.g., 1.0.0-0)
Feature branches: MAJOR.MINOR.PATCH-FEATURE-BUILD (e.g., 1.0.0-beta-5)

Semantic versioning is enforced:
- Same MAJOR.MINOR: highest PATCH is auto-selected
- Different MAJOR: build fails (breaking API change)
- Circular dependencies are detected and rejected

The code example below shows a complete package definition with dependencies.

See 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.

defines module qa.getting.started.manage.dependencies

  defines package

    version as Version: 1.0.0-0
    description as String = "Example package showing dependency management"
    license <- "MIT"
    publicAccess <- true

    tags <- [
      "example",
      "getting-started"
    ]

    applyStandardIncludes <- true

    deps <- {
      "ekopen.network.support.utils": "1.6.1-9",
      "ekopen.net.handy.tools": "3.2.1-0"
    }

    devDeps <- {
      "ekopen.org.supertools.util": "4.6.1-6"
    }

    excludeDeps <- {
      "ekopen.some.unwanted.pack": "ekopen.org.supertools.util"
    }

  defines program
    ManageDependencies()
      stdout <- Stdout()

      stdout.println("Package with dependencies defined")
      stdout.println("Run: ek9 -Dp thisFile.ek9 to resolve")
      stdout.println("Run: ek9 -PV thisFile.ek9 to see version")

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.

beginner cargo dependencies deploy deps devDeps first gradle import install intro library maven mvn npm package pip pom publish semantic start version versioning

import library
external package
third party library
defines package


Question: How do I tag my package so it can be easily found in remote repositories?

How do I make my EK9 package discoverable?

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.

Declare tags as a List literal:
  tags <- [
    "networking",
    "http",
    "rest-client"
  ]

Tag best practices:
- Use lowercase with hyphens for multi-word tags
- Include the problem domain ("networking", "database", "parsing")
- Include the technology area ("http", "json", "csv")
- Include the package role ("client", "server", "utility", "middleware")
- Keep tags specific: "http-client" is more useful than "tools"
- Limit to 5-10 tags: too many dilutes discoverability

Combined with `description` and `publicAccess`, tags form the discovery profile:
  description as String = "Lightweight HTTP client with retry and timeout support"
  publicAccess <- true
  tags <- [ "http", "client", "networking", "rest" ]

Setting `publicAccess <- false` makes the package private: it can still be deployed to a private repository but will not appear in public searches.

The code example below shows a well-tagged package ready for repository discovery.

See Q7 for managing dependencies. See Q9 for licensing. See Q13 for packages vs modules.

defines module qa.getting.started.tag.packages

  defines package

    version as Version: 1.0.0-0
    description as String = "Lightweight HTTP client with retry and timeout support"
    license <- "MIT"
    publicAccess <- true

    tags <- [
      "http",
      "client",
      "networking",
      "rest",
      "retry"
    ]

    applyStandardIncludes <- true

  defines program
    TagPackages()
      stdout <- Stdout()

      stdout.println("Package tagged for repository discovery")
      stdout.println("Tags help other developers find your library")

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.

beginner cargo categorize discoverable first intro keywords library metadata npm package publicAccess pypi repository search start tags

defines program
    TagPackages()
      stdout <- Stdout()
    TagPackages()
      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.


Question: How can I control the license agreement for my package's code?

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?

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.

Declare the license:
  license <- "MIT"

Common open source licenses:
  "MIT"            permissive, minimal restrictions
  "Apache-2.0"     permissive with patent grant
  "GPL-3.0"        copyleft, derivatives must also be GPL
  "BSD-2-Clause"   permissive, simple
  "LGPL-3.0"       copyleft for libraries, linking permitted
  "MPL-2.0"        file-level copyleft

For proprietary code:
  "Proprietary"    or your company's license name

The license field works together with `publicAccess`:
  publicAccess <- true     anyone can download and use
  publicAccess <- false    private, restricted distribution

A package with `publicAccess <- true` and `license <- "MIT"` tells consumers they can freely use, modify, and redistribute the code with attribution.

A package with `publicAccess <- false` and `license <- "Proprietary"` is restricted to authorized users only.

The 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.

The code example below shows two typical licensing configurations.

See Q7 for managing dependencies. See Q8 for tagging packages.

defines module qa.getting.started.package.licensing

  defines package

    version as Version: 1.0.0-0
    description as String = "Open source utility with MIT license"
    license <- "MIT"
    publicAccess <- true

    tags <- [
      "utility",
      "open-source"
    ]

    applyStandardIncludes <- true

  defines program
    PackageLicensing()
      stdout <- Stdout()

      stdout.println("Package license: MIT")
      stdout.println("Public access: true")
      stdout.println("Anyone can use, modify, and redistribute with attribution")

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.

MIT beginner cargo copyright distribution first intro license npm open-source package proprietary publicAccess spdx start terms

defines program
    PackageLicensing()
      stdout <- Stdout()
    PackageLicensing()
      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.


Question: How do I distinguish between development dependencies and production dependencies?

What is the difference between deps and devDeps?

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`:

`deps` - production dependencies:
  deps <- {
    "ekopen.network.utils": "1.6.1-9"
  }
  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).

`devDeps` - development-only dependencies:
  devDeps <- {
    "ekopen.test.helpers": "2.0.0-0"
  }
  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.

Key differences:
- `deps` are transitive: your consumers inherit them
- `devDeps` are NOT transitive: your consumers never see them
- `deps` are included in -P (package) and -D (deploy)
- `devDeps` are excluded from packaged artifacts
- `deps` are resolved by -Dp for everyone
- `devDeps` are only resolved for the package owner

Rule of thumb:
- If your code needs it to RUN: put it in `deps`
- If your code only needs it to TEST or BUILD: put it in `devDeps`

Both use the same Dict literal syntax with module name to version mapping.

The code example below shows a package with both production and development dependencies clearly separated.

See Q7 for managing dependencies. See Q11 for excluding transitive dependencies.

defines module qa.getting.started.dev.vs.production.deps

  defines package

    version as Version: 1.0.0-0
    description as String = "Shows deps vs devDeps separation"
    license <- "MIT"
    publicAccess <- true

    tags <- [
      "example"
    ]

    applyStandardIncludes <- true

    //Production dependencies: included in deployed package
    deps <- {
      "ekopen.network.support.utils": "1.6.1-9",
      "ekopen.net.handy.tools": "3.2.1-0"
    }

    //Development-only: test frameworks, mocks, tools
    devDeps <- {
      "ekopen.org.supertools.util": "4.6.1-6",
      "ekopen.org.net.tools.misc": "3.2.3-21"
    }

  defines program
    DevVsProductionDeps()
      stdout <- Stdout()

      stdout.println("deps: included in production, transitive to consumers")
      stdout.println("devDeps: development only, never shipped to consumers")

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.

beginner dependencies deploy deps devDependencies devDeps development first gradle intro maven npm package production scope start test testImplementation transitive

defines program
    DevVsProductionDeps()
      stdout <- Stdout()
    DevVsProductionDeps()
      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.


Question: What if there are transitive dependencies I do not want to use?

How do I exclude a transitive dependency?

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.

EK9 provides `excludeDeps` inside `defines package` for this:
  excludeDeps <- {
    "unwanted.module.name": "parent.that.brings.it.in"
  }

The 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).

Example scenario:
  Your package depends on `ekopen.network.utils` version 1.6.1-9.
  That library depends on `ekopen.old.logging` version 0.9.0-0.
  You do not want `ekopen.old.logging`. So you write:

  excludeDeps <- {
    "ekopen.old.logging": "ekopen.network.utils"
  }

This tells the dependency resolver: when processing `ekopen.network.utils`, skip its dependency on `ekopen.old.logging`.

EK9 also protects you automatically:
- Circular dependencies are detected and rejected at resolve time
- Version rationalization selects the highest compatible PATCH version
- Major version conflicts (different MAJOR numbers) fail the build
- Duplicate dependency entries are handled gracefully

Run `ek9 -Dp -v app.ek9` to resolve and see which dependencies were accepted and which were rejected (with reasons like MANUAL, RATIONALISATION, OPTIMISED).

The code example below shows a package that excludes an unwanted transitive dependency.

See Q7 for managing dependencies. See Q10 for dev vs production dependencies. See Q14 for listing resolved dependencies.

defines module qa.getting.started.exclude.transitive.deps

  defines package

    version as Version: 1.0.0-0
    description as String = "Shows how to exclude transitive dependencies"
    license <- "Apache-2.0"
    publicAccess <- true

    tags <- [
      "example"
    ]

    applyStandardIncludes <- true

    deps <- {
      "ekopen.network.support.utils": "1.6.1-9",
      "ekopen.net.handy.tools": "3.2.1-0"
    }

    //Exclude: block ekopen.some.unwanted.pack from being pulled in
    //via ekopen.network.support.utils
    excludeDeps <- {
      "ekopen.some.unwanted.pack": "ekopen.network.support.utils"
    }

  defines program
    ExcludeTransitiveDeps()
      stdout <- Stdout()

      stdout.println("Transitive dependency excluded")
      stdout.println("Run: ek9 -Dp thisFile.ek9 to resolve and verify")

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.

beginner conflict dependency exclude excludeDeps exclusions first gradle indirect intro maven resolve start transitive unwanted version


Question: How do I install EK9?

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.

Installation steps:

1. Install Java 25+:
   Download and install Azul Zulu JDK 25 (or any Java 25+ JDK). Verify with:
     javac -version
   Must show version 25 or higher.

2. Get the EK9 compiler:
   The compiler is distributed as two files:
   - `ek9` (or `ek9.exe` on Windows): a lightweight native wrapper
   - `ek9c-jar-with-dependencies.jar`: the compiler itself

   Place both in the same directory and add that directory to your PATH.

3. Verify installation:
     ek9 -V                    show compiler version
     ek9 -h                    show help
     ek9 -H                    list all help keywords

Alternative setup with EK9_HOME:
   Instead of placing both files together, set the EK9_HOME environment variable to point to the directory containing the JAR:
     export EK9_HOME=/opt/ek9
   The `ek9` wrapper checks EK9_HOME first, then looks relative to its own location.

Memory configuration:
   EK9_COMPILER_MEMORY="-Xmx1g"       compiler memory (default: 512m)
   EK9_APPLICATION_MEMORY="-Xmx2g"    program memory (default: 512m)

For compiler developers (building from source):
     git clone https://github.com/stephenjohnlimb/ek9.git
     cd ek9
     mvn clean install
   The JAR appears at compiler-cli/target/ek9c-jar-with-dependencies.jar
   The native wrapper is built via CMake during the Maven build.

On Linux/macOS, EK9 files with the #!ek9 shebang can run directly:
     chmod u+x hello.ek9
     ./hello.ek9

The code example below is a quick installation verification program.

See Q2 for compiling and running programs. See Q21 for the built-in help system. See Q252 for verbose and debug compilation modes.

defines module qa.getting.started.install.ek9

  defines program
    VerifyInstallation()
      stdout <- Stdout()

      stdout.println("EK9 is installed and working!")
      stdout.println("Try: ek9 -V for version info")
      stdout.println("Try: ek9 -h String for type help")
      stdout.println("Try: ek9 -H for all help keywords")

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.

Azul EK9_HOME JDK Java PATH beginner download first install intro migrate prerequisites setup start version

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.


Question: What is the difference between a package and a module in EK9?

When do I use defines module vs defines 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.

A MODULE is a namespace for organizing code:
  defines module com.example.utilities
Every .ek9 file declares exactly one module. Multiple files can share the same module name, and everything within the same module is automatically visible across those files. Modules control code organization, visibility, and the `references` mechanism for cross-module access.

A PACKAGE is optional metadata for distribution:
  defines package
    version as Version: 1.0.0-0
    description as String = "..."
    deps <- { ... }
A 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.

Key differences:
- Module: REQUIRED in every file. Organizes code into namespaces.
- Package: OPTIONAL. Only needed when you want to distribute your code as a library.

When you need a package:
- You are publishing a library for others to depend on
- You need version management (-IV, -SV, -PV)
- You want to declare dependencies on other libraries
- You want to deploy to a repository (-D)

When you do NOT need a package:
- You are writing an application (not a library)
- Your code has no external dependencies
- You are writing scripts or standalone programs

Think of it this way:
- Module = the code itself and its namespace
- Package = the shipping label on that code

Related questions for package specifics:
- How do I manage dependencies? (Q7)
- How do I tag my package for discovery? (Q8)
- How do I set a license? (Q9)
- How do I separate dev vs production dependencies? (Q10)
- How do I exclude transitive dependencies? (Q11)

The 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.

See Q6 for organizing modules. See Q2 for compile and run.

defines module qa.getting.started.package.vs.module

  defines function
    greet() as pure
      -> name as String
      <- message as String: `Hello, ${name}!`

  defines program
    PackageVsModule()
      stdout <- Stdout()

      //This module works fine without a package block
      //Add defines package only when you need to distribute it
      stdout.println(greet("World"))
      stdout.println("This module has no package: it is a standalone program")

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.

beginner defines deploy difference distribution first intro library module namespace organize package start version


Question: How do I see what dependencies my project has resolved?

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:
  ek9 -Dp -v app.ek9

The `-Dp` flag resolves all dependencies and `-v` (verbose) displays the results. The output shows two sections:

1. Applied: dependencies that passed validation and will be used
   Each shown as `moduleName-version`

2. Not Applied: dependencies that were rejected, with reasons
   Each shown as `moduleName-version(REASON)`

Rejection reasons:
  MANUAL           you explicitly excluded it via excludeDeps
  RATIONALISATION  a higher compatible version was selected instead
  SAME_VERSION     duplicate entry, already included
  OPTIMISED        parent dependency was rejected, so this is orphaned

The resolver also detects and reports:
- Circular dependencies (build fails)
- Semantic version breaches (different MAJOR versions, build fails)

Important: `-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.

There 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.

Related questions:
- How do I manage dependencies? (Q7)
- How do I exclude transitive dependencies? (Q11)
- What is the difference between a package and a module? (Q13)

See Q7 for managing dependencies. See Q15 for semantic versioning.

defines module qa.getting.started.list.resolved.deps

  defines package

    version as Version: 1.0.0-0
    description as String = "Example showing dependency resolution output"
    license <- "MIT"
    publicAccess <- true

    tags <- [
      "example"
    ]

    applyStandardIncludes <- true

    deps <- {
      "ekopen.network.support.utils": "1.6.1-9",
      "ekopen.net.handy.tools": "3.2.1-0"
    }

    excludeDeps <- {
      "ekopen.some.unwanted.pack": "ekopen.network.support.utils"
    }

  defines program
    ListResolvedDeps()
      stdout <- Stdout()

      stdout.println("Run: ek9 -Dp -v thisFile.ek9")
      stdout.println("To see applied and rejected dependencies")

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.

accepted beginner cargo circular dependencies first intro list maven migrate mvn npm pip rationalization rejected resolved start tree verbose version


Question: What is semantic versioning and why must I use it in EK9 packages?

Why does EK9 enforce semantic versioning?

What do the version numbers mean in defines package?

I use Maven versions or npm version, how does EK9 handle versioning?

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.

Version format: MAJOR.MINOR.PATCH-BUILD
  version as Version: 2.1.3-0

What each part means:
  MAJOR  breaking API changes (consumers must update their code)
  MINOR  new features, backwards compatible (safe to upgrade)
  PATCH  bug fixes, backwards compatible (safe to upgrade)
  BUILD  auto-incremented on every build attempt

Feature branch format: MAJOR.MINOR.PATCH-FEATURE-BUILD
  version as Version: 2.1.3-beta-5
  Used for pre-release or experimental work.

Why EK9 ENFORCES this:
- 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.
- Different MAJOR versions cause the build to FAIL. This protects you from silently using an incompatible API.
- Without enforced semver, automatic version rationalization would be unsafe.

CLI commands for version management:
  ek9 -IV major app.ek9     increment major (resets minor, patch, build)
  ek9 -IV minor app.ek9     increment minor (resets patch, build)
  ek9 -IV patch app.ek9     increment patch (resets build)
  ek9 -IV build app.ek9     increment build number only
  ek9 -SV 2.0.0 app.ek9    set version explicitly (zeros build)
  ek9 -SF 2.0.0-rc app.ek9  set feature version
  ek9 -PV app.ek9           print current version

Common workflow:
  1. Develop and test
  2. ek9 -IV patch app.ek9   bump patch for bug fix release
  3. ek9 -P app.ek9          package (uses -O3)
  4. ek9 -D app.ek9          deploy

Related questions:
- How do I manage dependencies? (Q7)
- How do I see resolved dependencies? (Q14)

See Q7 for managing dependencies. See Q14 for listing resolved dependencies. See Q270 for supply chain security.

defines module qa.getting.started.semantic.versioning

  defines package

    version as Version: 2.1.3-0
    description as String = "Demonstrates semantic versioning in EK9"
    license <- "MIT"
    publicAccess <- true

    tags <- [
      "example",
      "versioning"
    ]

    applyStandardIncludes <- true

  defines program
    SemanticVersioning()
      stdout <- Stdout()

      stdout.println("Package version: 2.1.3-0")
      stdout.println("MAJOR=2 MINOR=1 PATCH=3 BUILD=0")
      stdout.println("Run: ek9 -PV thisFile.ek9 to see current version")
      stdout.println("Run: ek9 -IV minor thisFile.ek9 to bump to 2.2.0-0")

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.

beginner breaking build cargo compatibility first increment intro major maven minor mvn npm package patch semantic semver start version versioning

defines program
    SemanticVersioning()
      stdout <- Stdout()
    SemanticVersioning()
      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.


Question: How does indentation work in EK9?

How many spaces per indent level in EK9?

Why does EK9 reject odd numbers of spaces?

Answer:
EK9 uses significant indentation to define scope, similar to Python. There are no braces, no semicolons, and no end keywords.

The rules are strict and enforced by the lexer:

1. Exactly 2 spaces per indentation level
   Level 0: no indent (module declaration)
   Level 1: 2 spaces (construct blocks like defines function)
   Level 2: 4 spaces (function names, class names)
   Level 3: 6 spaces (function body)
   And so on.

2. Tabs are NOT allowed
   The lexer rejects tabs with: "Tabs not supported for indentation; use spaces"
   Configure your editor to insert spaces when you press Tab.

3. Odd numbers of spaces are NOT allowed
   The lexer rejects odd space counts with: "Odd number of spaces for indentation"
   Indentation must always be a multiple of 2. A line indented by 3 or 5 spaces is an error.

4. Indentation must increase by exactly one level
   You cannot jump from level 1 (2 spaces) to level 3 (6 spaces).
   Each nested block increases by exactly 2 spaces.

5. Dedenting can drop multiple levels at once
   Returning from deeply nested code can decrease by several levels in one step.

The 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.

Editor setup:
- Set tab key to insert 2 spaces (not a tab character)
- Enable "show whitespace" to catch mixed indentation
- The EK9 VSCode extension handles this automatically
- In the REPL, pressing Tab inserts 2 spaces

The code example below shows correct indentation at multiple nesting levels.

See Q5 for source file structure. See Q49 for function definitions that demonstrate indentation levels. See Q622 for automatic code formatting.

defines module qa.getting.started.indentation

  defines function

    outerFunction()
      -> innerValue as Integer
      <- rtn as String: String()

      if innerValue > 0
        rtn: "positive: " + $innerValue
      else
        rtn: "non-positive"

  defines class

    IndentExample
      name as String: "default"

      IndentExample()
        -> n as String
        name :=: n

      describe()
        <- rtn as String: `IndentExample(${name})`

      default operator ?

  defines program
    Indentation()
      stdout <- Stdout()

      example <- IndentExample("two spaces per level")
      stdout.println(example.describe())
      stdout.println(outerFunction(42))

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.

beginner braces dedent first indent indentation intro level migrate nesting scope spaces start tabs whitespace

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.

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.


Question: Where is the main function in EK9?

Can I have multiple programs in one file?

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.

A program is a named method inside defines program:

  defines program
    MyProgram()
      stdout <- Stdout()
      stdout.println("Hello")

Key points:

1. Programs are NAMED, not anonymous main functions
   You choose the name: HelloWorld(), DataProcessor(), WebServer(). This is more descriptive than a generic main() entry point.

2. Multiple programs can exist in one file
   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.

3. Programs can accept typed command-line arguments
   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.

4. Programs share module resources
   All programs in a module can access the module's functions, classes, constants, and types.

The code example below shows two programs in one module: a simple greeting and one that accepts a name argument.

See 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.

defines module qa.getting.started.entry.point

  defines function

    greetingMessage()
      <- message as String: "Welcome to EK9"

  defines program

    SimpleGreeting()
      stdout <- Stdout()
      stdout.println(greetingMessage())

    PersonalGreeting()
      -> name as String
      stdout <- Stdout()
      stdout.println(`Hello, ${name}!`)

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.

arguments beginner defines entry first function intro main multiple parameters point program run start

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.


Question: How do I add comments to my code?

Does EK9 support doc comments like Javadoc?

Answer:
EK9 supports four comment styles:

1. Single-line comments: //
   The most common style. Everything after // until end of line is ignored.
   Example: // This is a comment

2. Documentation comments: opened with <?- and closed with its mirror-image marker
   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.

3. General block comments: <!- ... -!>
   Used for internal implementation notes NOT intended for documentation extraction. Can span multiple lines.

4. HTML-style block comments: <!-- ... -->
   An alternative to general block comments for developers familiar with HTML syntax. Functionally identical to the exclamation-mark style.

All comment types are processed by the lexer and completely removed before parsing. The parser never sees comment content.

EK9 does NOT support C-style /* */ comments. Use one of the four styles above instead.

Best practices:
- Use // for quick inline notes
- Use documentation comments for type and function documentation
- Use block comments for temporarily disabling code or internal notes

See Q5 for source file structure where comments fit in the file layout.

defines module qa.getting.started.comments

  <?- Documentation comment: describes this function block -?>
  defines function

    <!- General block comment: internal implementation note -!>
    describeComments()
      <- result as String: "comments example"
      // Single-line comment: the most common style
      result: "EK9 has four comment styles"

  <!-- HTML-style block comment: alternative syntax -->
  defines program
    Comments()
      stdout <- Stdout()
      stdout.println(describeComments())

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.

beginner block comment comments doc docstring documentation first inline intro javadoc migrate start

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.


Question: Can I use EK9 for scripting?

How do I pass command-line arguments to an EK9 program?

How do I pass parameters to a program in EK9?

How do I get input from the command line?

Answer:
EK9 supports script-like usage with a compile-and-run model.

Every EK9 file starts with #!ek9 as the first line. This shebang line follows Unix conventions for script identification.

Running EK9 programs:
  ek9 script.ek9                    Run the program
  ek9 script.ek9 arg1 arg2          Pass arguments
  ek9 -r SpecificProgram script.ek9 Run a named program

Program arguments are typed. Declare parameters and EK9 automatically converts command-line strings:

  defines program
    ProcessFile()
      -> filename as String
      ...

  Run with: ek9 script.ek9 mydata.txt

Supported argument types include String, Integer, Float, Boolean, Date, Time, DateTime, Duration, and more. EK9 handles the string-to-type conversion automatically.

EK9 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.

Direct execution as a script:
  chmod u+x script.ek9
  ./script.ek9 arg1 arg2

Because 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.

Multiple related programs can live in one file, making it easy to group CLI utilities together.

See Q1 for a minimal program. See Q2 for compile and run commands. See Q17 for entry points and named programs.

defines module qa.getting.started.scripting

  defines program

    Greeting()
      -> name as String
      stdout <- Stdout()
      stdout.println(`Hello, ${name}!`)

    AddNumbers()
      ->
        a as Integer
        b as Integer
      stdout <- Stdout()
      result <- a + b
      stdout.println(`Sum: ${result}`)

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.

argc arguments argv beginner cli command-line execute first input intro migrate parameters pass program run script scripting shebang start

scripting
command line arguments
program arguments
pass parameters
argv
program input
typed arguments

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.


Question: What file extension does EK9 use?

What is the file extension for EK9 source code?

Does the file name need to match the module name?

Answer:
EK9 source files use the .ek9 extension.

Key points about EK9 files:

1. Extension: .ek9
   All EK9 source files must end with .ek9. The compiler will not process files with other extensions.

2. File name does NOT need to match the module name
   Unlike Java (where MyClass.java must contain class MyClass), EK9 file names are independent of module names. You can name files descriptively: utilities.ek9, web_server.ek9, data_model.ek9.

3. One module per file
   Each .ek9 file contains exactly one defines module declaration. This keeps the mapping between files and modules clear.

4. File structure
   Every file starts with #!ek9, followed by optional comments or metadata headers, then defines module, then construct blocks (defines function, defines class, defines program, etc.).

5. Convention
   Use lowercase names with underscores for descriptive file names. Group related files in directories that mirror your module hierarchy.

See Q5 for source file structure. See Q6 for organizing modules across files.

defines module qa.getting.started.file.extension

  defines function

    fileInfo()
      <- info as String: "This file is named QA0020_file_extension.ek9"

  defines program
    FileExtension()
      stdout <- Stdout()
      stdout.println(fileInfo())

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.

beginner convention ek9 extension file first intro migrate module naming source start structure

Explanation: EK9 Stdout has println() for line output, not write(). Triggers E50060 — method not resolved. See ek9 -h Stdout for the full API.


Question: How do I get help on a specific EK9 type or keyword?

How do I look up EK9 type information from the command line?

Answer:
EK9 has a comprehensive built-in help system accessible from the command line.

Core commands:
  ek9 -h <keyword>    Show help for a specific topic
  ek9 -H              List ALL available help keywords
  ek9 -h quickstart   Show a getting-started guide with Hello World

What you can look up:

1. Built-in types: ek9 -h String, ek9 -h List, ek9 -h Dict, ek9 -h Optional
   Shows constructors, methods, operators, and usage examples.

2. Language constructs: ek9 -h class, ek9 -h function, ek9 -h trait
   Explains the construct with syntax guidance.

3. Operators: ek9 -h :=, ek9 -h ?, ek9 -h $
   Describes what each operator does and when to use it.

4. Error codes: ek9 -h E05030
   Explains what the error means and how to fix it.

5. Concepts: ek9 -h lambda, ek9 -h generic, ek9 -h stream
   Bridges concepts from other languages to EK9 equivalents.

Fuzzy matching:
   The help system tolerates typos. If you type ek9 -h Strng, it resolves to String and shows the correct help.

Q&A system:
  ek9 -q <words>      Search for answers using natural language
  ek9 -q              List all Q&A entries by category

Error detail levels:
  -E0 (default) compact errors, -E1 visual with snippets, -E2 with suggestions, -E3 full explanations.

See Q23 for the full catalog of built-in types you can look up. See Q2 for all CLI command options.

defines module qa.getting.started.help.system

  defines program
    HelpDemo()
      stdout <- Stdout()
      stdout.println("Use 'ek9 -h String' to see String's full API")
      stdout.println("Use 'ek9 -H' to list all help topics")
      stdout.println("Use 'ek9 -q' to search the Q&A knowledge base")

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.

api beginner code documentation error first help intro javadoc keyword lookup reference search start system type

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.


Question: How do I declare a variable in EK9?

What are the different ways to declare variables in EK9?

How does type inference work for local variables?

What is the difference between <- and := and :=? operators?

Answer:
EK9 provides several ways to declare variables and uses modifiers to control visibility, purity, and extensibility.

VARIABLE DECLARATIONS

1. Type inference with <- (idiomatic)
  name <- "Steve"           Inferred as String
  count <- 42                Inferred as Integer

2. Explicit type
  name as String: "Steve"
Use when declaring base types for polymorphism.

3. Reassignment operators
  name <- "Steve"       Declaration (new variable)
  name := "John"        Reassignment (existing variable)
  name :=? "Default"    Guarded: only if currently unset

4. Class/record fields
  greeting as String: "Hello"    Private by default

5. Parameters and returns (explicit types required)
  formatName() as pure
    -> first as String
    <- result as String: first

6. Named parameters
  result <- formatName(first: "Steve", last: "Limb")
If ONE is named, ALL must be named, in declared order. Essential for dynamic functions:
  multiplier <- (factor: count) is abstractOp as pure function
    result :=? operand * factor

MODIFIERS
  public/protected/private   Access (default: package-private)
  pure         No side effects, enforced on overrides
  abstract     Must be overridden
  override     Required when replacing parent method
  open         Allows extension (closed by default)
  default      Auto-generates operators
  dispatcher   Multi-method dispatch by argument type
  sanitized    Input sanitization at call site

Naming: generic names ('value', 'data', 'item') rejected (E11031).

See 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.

defines module qa.variables.declarations

  defines function

    formatName() as pure
      ->
        first as String
        last as String
      <- result as String: `${first} ${last}`

    abstractOp() as pure abstract
      -> operand as Integer
      <- result as Integer?

  defines class

    Greeter
      greeting as String: "Hello"

      Greeter()
        -> g as String
        greeting :=: g

      greet()
        -> name as String
        <- message as String: `${greeting}, ${name}`

      default operator ?

  defines program
    Declarations()
      stdout <- Stdout()

      // Type inference - idiomatic for local variables
      name <- "Steve"
      count <- 42

      // Explicit type - useful when declaring a base type
      label as String: "Count"

      // Reassignment
      name := "John"

      // Using the explicitly typed variable
      stdout.println(`${label}: ${count}`)

      // Named parameters at call site
      full <- formatName(first: name, last: "Limb")
      stdout.println(full)

      // Dynamic function with named captures
      multiplier <- (factor: count) is abstractOp as pure function
        result :=? operand * factor

      stdout.println($multiplier(3))

      greeter <- Greeter("Welcome")
      stdout.println(greeter.greet(name))

Java: Type name = value with all classes open by default, Python: name = value with duck typing, Rust: let name: Type = value with immutability by default, Go: var name Type = value or name := value. EK9: <- for declaration with inference, explicit Type for API boundaries, := for reassignment, :=? for guarded assignment. Modifiers: Java has public/private/protected/abstract/final/static, EK9 replaces final with closed-by-default and has no static. EK9 adds pure, dispatcher, sanitized, and open.

abstract assignment beginner declaration declare default dispatcher first guard immutable inference intro isset migrate modifier named null-safe open override parameter private protected public pure return safe sanitized side-effect start variable virtual

declare variable
variable declaration
create variable

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.

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.

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.


Question: What basic types does EK9 have?

Does EK9 have functional types like Function or Predicate?

Answer:
EK9 has a rich set of built-in types organized into several categories.

PRIMITIVES
  String     Text values: "Hello"
  Integer    Whole numbers: 42
  Float      Decimal numbers: 3.14
  Boolean    true or false
  Character  Single character: 'A'
  Bits       Bit manipulation
  Void       No value (used in type signatures)

RICH VALUE TYPES
These go far beyond what most languages provide as built-ins:
  Date, Time, DateTime    Calendar and clock values
  Duration, Millisecond   Time spans
  Money                   Currency-aware: 9.99#USD, 100#GBP
  Colour                  Colour values: #FF8800
  Dimension               Physical measurements: 100cm, 5.2kg
  Resolution              Display resolution
  Regex                   Regular expressions
  GUID                    Globally unique identifiers
  HMAC                    Cryptographic message authentication
  Version                 Semantic version numbers
  Path                    Geometric paths
  Locale                  Internationalization locale
  JSON                    JSON data handling

GENERIC COLLECTION TYPES
Parameterized with 'of' syntax:
  List of T               Ordered collection: List() of String
  Dict of (K, V)          Key-value mapping: Dict() of (String, Integer)
  DictEntry of (K, V)     Single key-value pair
  Optional of T           Value that may be absent
  Result of (O, E)        Success or error outcome
  Iterator of T           Lazy sequence traversal
  PriorityQueue of T      Priority-ordered queue
  MutexLock of T          Thread-safe wrapper

GENERIC FUNCTION TYPES (Key Building Block)
Unlike Java where functional types were an afterthought (java.util.function added in Java 8), EK9 builds these into the language as first-class constructs. There are 16 generic function types: 8 pure and 8 non-pure.

Pure function types (no side effects):
  Function of (T, R)      Takes T, returns R
  Consumer of T            Takes T, no return
  Supplier of T            No input, returns T
  Predicate of T           Takes T, returns Boolean
  UnaryOperator of T       Takes T, returns T (same type)
  BiFunction of (T, U, R)  Takes T and U, returns R
  BiConsumer of (T, U)     Takes T and U, no return
  BiPredicate of (T, U)    Takes T and U, returns Boolean
  Comparator of T          Compares two T values, returns Integer

Non-pure function types (can have side effects, mutate state, perform I/O):
  Routine of (T, R)        Takes T, returns R
  Acceptor of T            Takes T, no return
  Producer of T            No input, returns T
  Assessor of T            Takes T, returns Boolean
  BiRoutine of (T, U, R)   Takes T and U, returns R
  BiAcceptor of (T, U)     Takes T and U, no return
  BiAssessor of (T, U)     Takes T and U, returns Boolean

The 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.

I/O TYPES
  Stdout, Stderr, Stdin    Console I/O
  TextFile                 File reading and writing
  FileSystem               File system operations
  FileSystemPath           File and directory paths

NETWORK TYPES
  TCP                      TCP socket communication
  UDP                      UDP datagram communication

SYSTEM TYPES
  OS                       Operating system information
  EnvVars                  Environment variable access
  Signals                  OS signal handling
  SystemClock              System time source
  GetOpt                   Command-line option parsing

SECURITY TYPES
  InputSanitizer           XSS, SQL injection, command injection protection
  Exception                Error handling with exit codes

All 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.

Use '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.

defines module qa.basic.types

  defines class

    CheckExtension extends Check

    Check as open
      default Check()

      override operator ? as pure
        <- rtn <- true

  defines function

    doubleIt() as pure
      -> operand as Integer
      <- result as Integer: operand * 2

  defines program
    BasicTypes()
      stdout <- Stdout()

      // Primitives
      greeting <- "Hello"
      count <- 42
      ratio <- 3.14
      isReady <- true
      letter <- 'A'

      // Rich value types
      today <- Date()
      amount <- 9.99#USD
      shade <- #FF8800
      width <- 100cm

      // Generic collection
      names <- List() of String
      names += "Alice"
      names += "Bob"

      // Generic function type - pure transformation
      transform <- doubleIt

      stdout.println(`${greeting} ${count} ${ratio}`)
      stdout.println(`Ready: ${isReady} Letter: ${letter}`)
      stdout.println(`Amount: ${amount}`)
      stdout.println(`Colour: ${shade}`)
      stdout.println(`Width: ${width}`)
      stdout.println(`Today: ${today}`)
      stdout.println(`Names: ${names}`)
      stdout.println(`Doubled: ${transform(21)}`)

      // Predicate - pure boolean test (built-in param names: t, r)
      isPositive <- (threshold: 0) is Predicate of Integer as pure function
        r :=? t > threshold

      if isPositive(count)
        stdout.println(`${count} is positive`)

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.

all available beginner built-in categories first intro migrate overview primitive start type types what

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.

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.


Question: How do I convert between types?

How do I handle different types without instanceof?

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.

TYPE PROMOTION: THE #^ OPERATOR
The promote operator converts a value to a wider compatible type. This happens automatically when you assign to a variable of a wider type:
  intValue <- 42
  floatResult as Float: intValue
The 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:
  Integer to Float (safe numeric widening)
  Character to String (single char to text)
  Date to DateTime (date to full timestamp)
  Millisecond to Duration (time unit widening)
You can define #^ on your own types for custom promotions.

STRING CONVERSION: THE $ OPERATOR
The $ operator converts any value to its String representation:
  count <- 42
  countText <- $count
Every 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}`

CONSTRUCTOR CONVERSION
Explicit conversion uses constructors that accept other types:
  parsed <- Integer("99")
  precise <- Float(42)
This is the explicit, intentional conversion path when you know what you want.

THE DISPATCHER: TYPE-SPECIFIC PROCESSING (Key Mechanism)
The 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.

Declare one method 'as dispatcher' taking a base type (typically Any). Then define private overloads for each specific type:
  describe() as dispatcher
    -> mainValue as Any
    <- rtn as String: "Unknown type"
  private describe()
    -> mainValue as Integer
    <- rtn as String: "Got an Integer"
  private describe()
    -> mainValue as Float
    <- rtn as String: "Got a Float"

At runtime, the actual type of the argument determines which overload executes. The Any handler acts as the default for unhandled types.

Dispatcher rules:
  Exactly ONE method marked 'as dispatcher' (the entry point)
  Overloads have the same name but different parameter types
  1 or 2 parameters only (not 0, not 3+)
  All overloads must match purity (all pure or all non-pure)
  Adding a new type means adding a new overload, not modifying existing code

DISPATCHER FOR EXCEPTION HANDLING
EK9 has only ONE catch block per try. There are no multi-catch blocks like Java's catch(IOException | SQLException e). Instead, use a dispatcher:
  try
    riskyOperation()
  catch
    -> ex as Exception
    handleError(ex)
  private handleError() as dispatcher
    -> ex as Exception
    <- rtn as String: "General error"
  private handleError()
    -> ex as AnException
    <- rtn as String: "Known error"
  private handleError()
    -> ex as OtherException
    <- rtn as String: "Other error"

This 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.

WHY NO INSTANCEOF OR CASTING? (Design Philosophy)
Needing 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.

In 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.

EK9 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.

Why this matters:
  No forgotten-case bugs when a new type is added
  No instanceof chains that grow with every new type
  No ClassCastException or casting errors at runtime
  Each type handler is a separate, testable method
  The compiler validates dispatcher consistency at compile time
  Forces genuine polymorphic design, not just syntax convenience

See 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.

See 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.

defines module qa.type.conversion

  defines class

    AnException extends Exception

      AnException()
        -> reason as String
        super(reason)

      default operator ?

    OtherException extends Exception

      OtherException()
        -> reason as String
        super(reason)

      default operator ?

    TypeProcessor

      describe() as dispatcher
        -> mainValue as Any
        <- rtn as String: "Unknown type"

      private describe()
        -> mainValue as Integer
        <- rtn as String: `Integer: ${mainValue}`

      private describe()
        -> mainValue as Float
        <- rtn as String: `Float: ${mainValue}`

      private describe()
        -> mainValue as String
        <- rtn as String: `String: ${mainValue}`

      private handleError() as dispatcher
        -> ex as Exception
        <- rtn as String: "General error: " + $ex

      private handleError()
        -> ex as AnException
        <- rtn as String: "Known error: " + $ex

      private handleError()
        -> ex as OtherException
        <- rtn as String: "Other error: " + $ex

      processWithErrorHandling()
        -> mainValue as Any
        <- rtn as String: String()

        stdout <- Stdout()
        try
          rtn: describe(mainValue)
        catch
          -> ex as Exception
          rtn: handleError(ex)
          stdout.println(rtn)

  defines program
    TypeConversionDemo()
      stdout <- Stdout()

      // Promotion: Integer to Float via #^ operator
      intValue <- 42
      floatResult as Float: intValue
      stdout.println(`Promoted: ${floatResult}`)

      // Promotion: Character to String
      letter <- 'Z'
      textResult as String: letter
      stdout.println(`Promoted: ${textResult}`)

      // String conversion with $ operator
      count <- 100
      countText <- $count
      stdout.println(`String: ${countText}`)

      // Constructor conversion
      parsed <- Integer("99")
      stdout.println(`Parsed: ${parsed}`)

      // Dispatcher: process different types
      processor <- TypeProcessor()

      items <- [1, 2.5, "hello"]
      for listItem in items
        description <- processor.describe(listItem)
        stdout.println(description)

      // Dispatcher for exception handling (single catch, dispatched)
      result <- processor.processWithErrorHandling(42)
      stdout.println(result)

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.

any beginner cast casting catch conversion convert dispatch dispatcher exception first handler instanceof intro migrate overload promote promotion sealed start type visitor

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.


Question: What is the promote operator?

How does automatic type promotion work in EK9?

How does EK9 handle implicit type conversion?

Why does EK9 only allow one level of promotion?

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.

HOW IT WORKS
When 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:
  intValue <- 42
  floatResult as Float: intValue
The compiler sees Integer assigned to Float, finds Integer's #^ operator returns Float, and automatically inserts the promotion call. The same applies to function calls:
  acceptsFloat(21)
The Integer literal 21 is automatically promoted to Float because acceptsFloat expects a Float parameter.

BUILT-IN PROMOTIONS
  Integer to Float     Safe numeric widening
  Character to String  Single character to text
  Date to DateTime     Date to full timestamp
  Millisecond to Duration  Time unit widening

USER-DEFINED PROMOTIONS
You can define #^ on your own types:
  Measurement
    operator #^ as pure
      <- rtn as String: some formatted text
A type can have only ONE #^ operator. This is by design.

SINGLE PROMOTION ONLY (Critical Rule)
The 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.

This 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.

COST-BASED METHOD MATCHING
When resolving which method overload to call, the compiler assigns costs:
  Exact type match:      0.0  (perfect, always preferred)
  Superclass match:      0.05 (inheritance)
  Trait match:           0.10 (interface implementation)
  Promotion match:       0.5  (via #^ operator)
  Any type match:        20.0 (universal fallback)
  No match:             -1.0  (invalid)

The 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.

WHY OTHER LANGUAGES GET THIS WRONG
Implicit conversion chains are one of the most prolific sources of subtle bugs across programming languages.

C++: 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.

JavaScript: 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.

Scala: 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.

Java: 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.

Python: While mostly explicit, __add__ returning NotImplemented triggers __radd__ on the other operand, which can create surprising dispatch chains with mixed types.

EK9'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.

See also Q24 (How do I convert between types?) for the full type conversion story including dispatcher and constructor conversion.

See 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.

defines module qa.promote.operator

  defines function

    acceptsFloat() as pure
      -> mainValue as Float
      <- rtn as Float: mainValue * 2.0

  defines class

    Measurement
      reading <- Float()

      Measurement()
        -> reading as Float
        this.reading :=: reading

      reading() as pure
        <- rtn as Float: reading

      operator #^ as pure
        <- rtn as String: `${reading}units`

      default operator ?

  defines program
    PromoteDemo()
      stdout <- Stdout()

      // Automatic promotion: Integer to Float in assignment
      intValue <- 42
      floatResult as Float: intValue
      stdout.println(`Promoted int to float: ${floatResult}`)

      // Automatic promotion: Integer to Float in function call
      doubled <- acceptsFloat(21)
      stdout.println(`Promoted in call: ${doubled}`)

      // Character to String promotion
      letter <- 'X'
      textValue as String: letter
      stdout.println(`Promoted char to string: ${textValue}`)

      // User-defined promotion
      sensor <- Measurement(98.6)
      displayText as String: sensor
      stdout.println(`Custom promote: ${displayText}`)

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.

beginner chain coerce coercion convert cost first implicit intro matching migrate operator overload promote promotion resolution signature single start widening

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.

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.


Question: Why does EK9 not have primitives?

How do I use primitives instead of objects in EK9?

Are Integer and Float objects or primitives 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.

You 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.

WHY OTHER LANGUAGES HAVE PRIMITIVES (AND THE PROBLEMS THEY CAUSE)

Java has two type systems that do not mix cleanly. int is a primitive, Integer is an object. This creates:
  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.
  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.
  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.
  Two equality systems: == compares identity for objects but value for primitives. The same operator means different things depending on whether autoboxing occurred.

C# 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.

C++ 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.

Go 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.

EK9'S UNIFIED OBJECT MODEL

In EK9, every value is an object with:
  Operators: ==, <>, <, >, <=, >=, <=>, $, #?, ? all work on every type
  Methods: every type has a consistent API discoverable via 'ek9 -h TypeName'
  Tri-state semantics: every object can be set, unset, or absent (see Q29)
  Polymorphism: Integer, Float, String can all be stored in collections, passed as Any, dispatched on

There 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.

  count <- 42
  ratio <- 3.14
  ready <- true
  letter <- 'A'

All four are objects. All four support the same operator patterns. All four can be put in a List of Any. All four have consistent tri-state behaviour with the ? operator.

PERFORMANCE
You 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.

See also Q23 (What basic types does EK9 have?) for the full type catalog. See Q93 for defining classes.

See Q23 for basic types. See Q7 for managing dependencies.

defines module qa.no.primitives

  defines program
    NoPrimitivesDemo()
      stdout <- Stdout()

      // All values are objects - no primitives
      count as Integer = 42
      ratio <- 3.14
      ready <- true
      letter <- 'A'

      // All support the same operator patterns
      stdout.println(`Count is set: ${count?}`)
      stdout.println(`Ratio is set: ${ratio?}`)
      stdout.println(`Ready is set: ${ready?}`)
      stdout.println(`Letter is set: ${letter?}`)

      // All can go in a heterogeneous collection
      items <- [count, ratio, ready, letter]
      for listItem in items
        stdout.println(`Item: ${listItem?}`)

      // All support $ for string conversion
      stdout.println(`${count} ${ratio} ${ready} ${letter}`)

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.

autoboxing beginner boxing first int integer intro migrate object primitive primitives reference start unboxing unified value wrapper

no primitives
everything is object
autoboxing

Explanation: int, float, char, bool do not exist as primitives.


Question: Is EK9 statically or dynamically typed?

Does EK9 check types at compile time or runtime?

Do I have to declare types for every variable in EK9?

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.

But EK9 does not feel like a statically typed language. Type inference means you rarely write type annotations for local variables:
  count <- 42
  name <- "Steve"
  ratio <- 3.14
  items <- List() of String
The compiler infers Integer, String, Float, and List of String. You get the safety of static typing with the brevity of dynamic typing.

WHEN YOU MUST DECLARE TYPES
Type annotations are required in specific places where inference is not possible or where clarity matters:
  Parameters: -> operand as Integer (always required)
  Return values: <- result as String (always required)
  Explicit base types: shape as Shape: Circle() (when you want the variable typed as the base)
  Properties: name <- String() (class/record fields)

WHEN YOU SHOULD NOT DECLARE TYPES
For local variables where the type is obvious from the right-hand side, let inference do the work:
  count <- 42 (not count as Integer: 42)
  name <- "hello" (not name as String: "hello")
  today <- Date() (not today as Date: Date())
Declaring the type when inference can determine it is redundant and reduces readability.

WHY NOT DYNAMIC TYPING?
Dynamic languages (Python, JavaScript, Ruby) defer type checking to runtime. This means:
  Python: name = 42; name.upper() compiles fine but crashes at runtime with AttributeError
  JavaScript: '5' - 3 gives 2 but '5' + 3 gives '53'. Type coercion rules are inconsistent and produce silent bugs.
  Ruby: method_missing can intercept any call, making it impossible to know at edit time whether a method exists

These 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.

EK9 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.

STRONG TYPING
EK9 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.

COMPILE-TIME GUARANTEES
Because EK9 is statically typed, the compiler can guarantee:
  Every method call resolves to an actual method
  Every operator is defined on the type it is applied to
  Every assignment is type-compatible (or has a valid promotion)
  Every function parameter matches the expected type
  Every return value matches the declared return type
  Generic types are fully resolved and checked

See 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.

See Q23 for basic types. See Q29 for unset variables. See Q257 for constrained types that restrict values at the type level.

defines module qa.static.typing

  defines function

    addValues() as pure
      ->
        first as Integer
        second as Integer
      <- result as Integer: first + second

  defines program
    StaticTypingDemo()
      stdout <- Stdout()

      // Type inference - compiler knows the types
      count <- 42
      name <- "Steve"
      ratio <- 3.14
      items <- List() of String

      items += "one"
      items += "two"

      // Explicit type when you want a base type
      floatValue as Float: count

      // All types checked at compile time
      result <- addValues(count, 10)
      stdout.println(`${name}: ${result}`)
      stdout.println(`Ratio: ${ratio}`)
      stdout.println(`Items: ${items}`)
      stdout.println(`Float: ${floatValue}`)

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.

anonymous beginner capture check closure compile dynamic first infer inference intro migrate runtime safety start static strong type typed typing

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.