Coming from another language?
This appendix is a lookup, not a tour. Find the thing you would reach for in Java, Kotlin, C#, Rust, Go, Python or TypeScript, and the table says what EK9 has in its place and which chapter explains it. Where the table says "none", that is a decision and not an omission: The Evidence gives the reason for each removal, and the first rows are the reflexes that cost newcomers the most time.
Reflex to EK9
| You reach for | EK9 has | Where it is taught |
|---|---|---|
null, None, nil |
no such keyword and no way to write one (E01073). A value is absent, unset or set, and ? asks whether it is set |
Values and the Three States; Optional and Result, in Depth |
Optional.get, unwrap, !!, .Value |
get() only inside a block whose check proved the value present (E08030 otherwise) |
Optional and Result, in Depth |
| optional chaining and null-coalescing operators | a <- guard on if, switch, for, while and try, and ?: for "this, or that when unset" |
Guards and Safe Code |
break, continue, return, goto |
none. Pipelines, guards, a declared return variable and multiple case values | Making Decisions and Repeating Work; Stream Pipelines in Depth |
switch with fall-through |
switch with several values per case; no fall-through |
Making Decisions and Repeating Work |
instanceof, casts, as, type patterns |
none. A dispatcher routes a call on the argument's run-time type |
The Dispatcher |
equals, hashCode, compareTo, toString, clone |
operators ==, #?, <=>, $, :=:; one sameness per type and the rest generated |
Using Operators; Operators and Pragmatic Purity, in Full |
| a new operator, or any meaning for an old one | a fixed set; a query operator must be pure and a mutating one must not be |
Operators and Pragmatic Purity, in Full |
final, const, val, immutable collections |
none. Every value is mutable; pure is a property of the function that does the work |
Operators and Pragmatic Purity, in Full |
lambdas, closures, Func<>, fn |
a function is a value; an inline one is a dynamic function, with a capture list when it uses the surrounding scope | Functions as Values: the Type Hierarchy; Dynamic Functions and Classes |
interface |
a trait (behaviour, no state) for composition; an abstract component for a wired dependency |
Traits and Composition; Components |
| inheriting from any class | closed by default, open only with as open (E05030 otherwise) |
Classes; Abstract Types, Subtypes and Polymorphism |
record, data class, struct |
defines record: public fields, operators, no methods |
Records and Enumerations |
enum with fields and methods |
an enumeration; constrain a base type with constrain as for a range |
Records and Enumerations; Constrained Types |
| generics with bounds | of type T for a type and of type (K, V) for several; a bare T gets only what Any offers |
Using Generics; Write-Your-Own Generic Types and Functions |
static methods and fields |
none. A module holds functions and constants | Functions |
import, using, use |
a module, and references to bring in another module's names, in alphabetical order (E11026) |
Organising a Growing Codebase |
checked or unchecked exceptions, panic, error values |
try/catch/finally for the unexpected, Result for the expected; the compiler closes a try resource on every path |
Errors and Exceptions |
synchronized, Mutex<T>, channels, async/await |
a MutexLock of T, entered with a MutexKey function; ` |
async` is a pipeline stage |
| Spring, Guice, Dagger | component, application and register; wiring is checked before the program runs |
Dependency Injection and Applications |
| AspectJ, decorators, middleware | with aspect of at the registration line |
Aspects |
JUnit, pytest, go test, cargo test |
an @Test program under dev/; assertion, black-box and parameterized tests share one runner |
Testing as You Go; Testing the Compiler Trusts |
| SonarQube, Checkstyle, ESLint, coverage plugins | limits that are compile errors, and a coverage bar that stops -P |
Quality Built In |
BigDecimal, a currency library |
Money, with its own literal |
Text, Numbers, Time and Money |
| Maven, Gradle, Cargo, npm, Go modules | a package block and ek9 -P; dependencies declared in the source |
Packaging and Sharing Your Program |
public static void main |
a program, with typed arguments |
Programs, Input and Output |
| string formatting and templates | a backtick string with ${expression} |
Text, Numbers, Time and Money |
| a web framework | a built-in service with HTTP verbs on its operations |
A Web Service, Step by Step |
Two of those rows hide a surprise worth saying aloud. EK9 has no immutable data structures, and that is deliberate: the safety
is in which function may change what (Operators and Pragmatic Purity, in Full), not in freezing the data, and a Rust or Haskell
programmer will notice the gap. And an Integer that overflows is unset and does not wrap, which makes an arithmetic overflow
a tri-state question and not a silent wrong number.
By language
Java and Kotlin. The obvious things carry over: classes, generics with type arguments, a single module namespace. The
reflexes that fail are equals/hashCode pairs, which are operators here and cannot disagree; open classes, which are closed
unless declared otherwise; checked-versus-unchecked exceptions, replaced by Result for what you expect; and Spring's runtime
wiring, replaced by checks the compiler makes. Kotlin's safe-call and elvis operators are the nearest relatives of a guard and of ?:, and
the difference is that the unsafe form does not exist (Optional and Result, in Depth).
C#. Func<> and Action<> are function types, and EK9 derives the equivalent from the shape of a function and whether it
is pure (Functions as Values: the Type Hierarchy). LINQ queries are stream pipelines. There is no await; concurrency is a
lock around a value and an async pipeline stage.
Rust. Result and exhaustive matching are familiar, and so is the wish to make misuse impossible. EK9 has no borrow checker
and no ownership annotations; shared mutable state is permitted only through a MutexLock, and the compiler's analysis covers the
shapes it accepts, which Concurrency, Safely states.
Go. Explicit error returns are replaced by Result for what you expect and an exception for what you do not. There are no
goroutines or channels; a | async stage and a MutexLock are the nearest. Table-driven tests become a parameterized @Test
whose table is files (Testing the Compiler Trusts), and go fmt is ek9 -f, one canonical format.
Python and TypeScript. The shift is largest here: every type is checked at compile time, and indentation is syntax, as in
Python, with ek9 -f giving every file one canonical layout. Comprehensions are pipelines, dictionaries are Dict, and a
decorator is an aspect at a registration line. None and undefined do not exist.
What is not here yet
Two things you would reasonably ask for are planned and are not available today, and the book does not teach them as working.
Calling Java libraries from EK9 is not yet available. EK9 programs compile to Java bytecode and run on the JVM, but a program cannot import and call a Java library. It is planned, and until it ships, what your program needs from the outside world is what the built-in types provide (Built-in Types at a Glance).
A public package repository, and deploying to one, is planned too. Packaging, installing from your own library and versioning are available today and are shown in Packaging and Sharing Your Program, along with the point at which a dependency resolves and the point at which it does not yet.