Introduction to EK9
EK9 is a general-purpose programming language that fuses Object Oriented and Functional programming with controlled Generic and Aspect Oriented Programming (AOP). It pulls widely used design patterns and framework ideas into the language, and the compiler itself provides a modular software ecosystem.
Its constructs are layered: they start very simple and build up to sophisticated software solutions.
An open question. Can one language fuse all these methodologies, and should it? Are the compromises severe enough to make the techniques less useful? Folding what are normally third-party libraries and build tools into one platform may look like a backwards step to many. Only time will tell.
Hello, World in EK9
#!ek9 defines module site.introduction.hello defines program HelloWorld() stdout <- Stdout() stdout.println("Hello, World")
Download introduction_hello.ek9
Save it as helloworld.ek9 and run it:
$ ek9 helloworld.ek9 # On Linux/Unix/MacOS you can use this as well/instead $ ./helloworld.ek9
The layout is minimal, much like Python. The module names a name space (site.introduction.hello), program names the kind of construct, and HelloWorld is the program with its body. <- declares a variable whose type is inferred: here Stdout, a class that prints Strings.
What's Different: EK9 vs Java/Python/C++
Coming from another language? EK9 departs on purpose from Java, Python, C++ and C#, based on decades of production bugs. These are not limitations: each departure removes a whole class of bug.
Three Critical Differences
| What's Missing/Different | Why It Matters | Learn More |
|---|---|---|
| No break/continue/return statements | These jumps compile cleanly and do the wrong thing (Apple's goto fail, 2014). Use stream
pipelines, guards and multiple case values instead. Each word is an error:
E01070, E01071,
E01072. |
Control Flow Philosophy |
| Tri-State Semantics (Absent/Unset/Set) | Not null vs not-null: a value is an object that is either set or unset, and absence (null) is
designed never to be observable - the compiler rejects reading a value that may not have been assigned.
There is no null (E01073). |
Tri-State Semantics |
| Types are Closed by Default | Use as open to allow extension; extending a closed type is
E05030. Built-in types (List, Dict) cannot be extended. Use composition and
traits instead. |
Closed by Default |
Why This Matters for Developers
You will reach for patterns from your previous language and get compiler errors. That is intentional: the compiler stops the bug before it happens. Give it a week and the patterns become natural.
Quick Start Advice:
- Day 1-2: expect frustration. Compile with the
-E3flag for rich error messages with examples. - Week 1: stream pipelines and guards feel natural. You stop reaching for break and return.
- Month 1: many of the mistakes you used to find at run time are now compile errors you fix before the program runs.
Main Features
Why try EK9? You will find most of its features in other modern languages. What is different is the effect of the syntax, the layering and the structure in combination.
General Features
- The language is indentation based (very similar to Python)
- Design is explicit, more like Java/C#/C++/Scala (limited duck typing in places)
- Constructs are laid out in groups, similar to Pascal
- It is compiled, not interpreted
- Punctuation is kept to a minimum (there is no semicolon as a statement terminator)
- Operators and operator overloading (more like C++)
- Defaulting of operators with a simple directive (similar to Java Lombok)
- It is very structured: no break, return, continue or GOTO!
- Uses a named return value
- Dynamic Functions and Classes (similar to Lambdas)
- Exceptions are Unchecked
- The compiler checks variable use in a manner similar to Golang/Rust
Specific new Features
- Compiled stream pipelining, similar to Unix '|' pipes in Unix shells
- Pragmatic type inference
- No primitive types - only Objects
- No global variables
- No type casting or instanceof - a dispatcher model is used instead, similar to Swift
- No null keyword
- Additional 'Unset' safe operators, based on the Elvis operator idea
- Language syntax for design by composition and delegation, not only inheritance
- Object and functional constructs as peers
- Abstract Functions, enabling polymorphic use of Functions/Delegates
- Inversion of Control and Dependency Injection built in (but constrained)
- Versioning as part of the language
- Packaging directives as part of the language
This page is long: it covers the rationale, the state of development and the licensing. To see the language first, start with the list of examples, or read structure, basics and then operators.
Treat this manual as a stream of consciousness; criticism accepted. Send suggestions for improvement to ek9lang@gmail.com.
There is a lot here; it takes time to understand it all. Besides the examples throughout, some sections hold code that solves general problems in different ways in EK9. Reviewing those helps as much as reading the pages.
Every example on these pages is compiled by the current compiler, and every output shown is captured from a real run. This is not theoretical ideas or vapour-ware.
Where to go next:
- New starters: start here.
- Experienced developers, or to see the language in more detail: what EK9 has to offer you.
- How mature is EK9 in tooling, compilers, IDE and community? See tooling.
TL;DR
For the licensing and the current state of development, jump to those sections or keep reading.
If you are new to software development and think you might use EK9, there is a brief overview of other languages to consider. It is not there to dissuade you from EK9, but to help you make an informed choice.
The same section outlines the mainstream current languages EK9 has taken design and structure from; each is excellent in its own right. What matters to any developer is supporting tools (an IDE), ease of syntax, a big library of functions, classes and components, and above all wide adoption: code examples and a supportive, informed developer community.
The purpose of creating EK9
EK9 is designed to reduce boilerplate code and punctuation, and it provides pragmatic type inference. Its default operator syntax has EK9 generate many simple operators for you.
It borrows constructs and syntax from other languages (layout, functions, classes, etc), so it should feel familiar, but it defines several new concepts. It also (re)introduces older Unix ideas such as streaming/pipelining, and limited operator overloading that Unix/Linux and C/C++ developers will know. The main additions over other languages are here.
There is no totally new concept that will change the world, but there are more abstractions and improvements. Everything EK9 offers could be written in C or assembly; the concepts and abstractions are the value being added, not ease of implementation. Design note: folding design patterns into the language makes the argument, from subroutines to components and from Spring's annotations to compile-time checks.
A language that is easy to read and to start with, and that also serves small, medium, large and very large applications, is a challenge. Some early examples may look trivial and easy in many other languages. Look further, especially in the structure and standard types sections: their examples start simple and become progressively more fully featured.
Why was it created?
Like most programming languages, EK9 fills a perceived gap. The 'hello world' example shows the influence of Unix, C, Java and Python. EK9 also takes features from older languages such as Haskell, newer ones such as Swift, and ones such as Java and C# that have proven robust and utilitarian over time. Long-lived design patterns, and the acceptance of HTTP/REST/HTML/CSS/JSON/SQL, also shaped the design.
Some languages (Java in particular) have struggled to incorporate new ideas elegantly (opinion); others, like Python, have not kept backwards compatibility in every release. That is not a criticism of either: once the DNA of a language is set, additions such as Generics and Functions never fit as fully as they would have if designed in from the outset.
Rather than retrofit these concepts into an existing language, experiments on paper tested how such diverse and seemingly incompatible ideas could be forged together. It became obvious that a new language was necessary. If you question the need to forge them together at all, read the other languages section: there are many good alternatives to EK9.
Third-party APIs and libraries using Annotations extend existing languages, but they are non-standard and not compiled and checked as part of the language, or their checks are delayed until run time. These drivers are why EK9 has Templates/Generics and Classes/Functions baked in from the start.
The Elephant in the Braces { }
Why indentation, as in Python, rather than the '{' '}' that C, C++, C#, Java, Javascript, Scala, Dart and Kotlin use to define blocks?
Because of ';}});': excessive, confusing punctuation. The Python-like layout does not suit everyone and has drawbacks - white space is invisible, which cuts both ways - but avoiding punctuation, especially {, [ and ( mixed together, is worth it.
Why EK9 does it this way. Clarity on the eye is the main reason EK9 follows Python and YAML in its layout.
The Current Situation in Development of EK9
EK9 is new and still unproven in production. The language is complete for its first release, the compiler implements it, and the tooling around it (IDE, debugger, test runner, coverage, profiling, an MCP server for AI assistants) works today. Every example on this site is compiled by the current compiler, and the ones that print are run and their output checked.
What it does not have yet: production users, a mature standard library, a body of third-party libraries, or a community to ask. The native back end (through LLVM) is well advanced but not complete.
A beta release is planned for mid-2027, for those prepared to take a risk on a new platform: researchers and academics, students and hobbyists, and startups that value catching mistakes at compile time over a large ecosystem. If you need a production platform with a large library ecosystem today, EK9 is not that yet.
Read on if:
- you are an experienced developer or tester and can see the potential here;
- you are looking for something new, interesting or challenging;
- you find the structure, layout or syntax of other languages frustrating or limiting;
- you think package management in other languages could be better;
- you expect compiler error messages to be much more useful.
Creating a modern language from scratch is a very long term undertaking and needs more than coders: testing and documentation are as critical as the language, the compiler and the IDE plug-ins. With so many good modern languages available, the bar for something new is very high; a language that is only slightly better won't be adopted.
Many languages take decades to establish themselves and some stay niche; others, like C, will probably be around forever. The hope is that EK9 proves useful to developers whose work suits it.
Why EK9 does it this way. EK9 is not meant to denigrate other languages or frameworks. It is another go at evolving programming by adding alternative constructs. It may succeed, find a niche, or not; but evolution should at least be attempted.
Current development and tooling
The language is defined for its first release and the compiler implements it: every example on these pages is compiled by it, and the ones that print are run and their output checked. The compiler builds an IR (intermediate representation) that a back end turns into output. Today's back end generates Java bytecode for the JVM. A native back end through LLVM, with its own C runtime, is well advanced: parity tests run the same programs on both back ends. It is not complete yet. The compiler has the concept of a target architecture, and the IR keeps other targets possible, such as wasm, JavaScript, or MSIL/CIL for Microsoft environments.
The rest of the tooling comes with the compiler, in the one ek9 command:
- Editing: the EK9 IDE (
ek9 -ide); a language server (ek9 -ls) used by the EK9 plugins for VS Code and IntelliJ; formatting and repair (-f,-ff,-fc); and a REPL (ek9 -repl). - Testing: a built-in test runner (
ek9 -t) with output for people, scripts, AI tools (JSON) and CI (JUnit XML); code coverage with an HTML report and an always-on 80% threshold; edge-case test generation and mutation testing; and grammar-based fuzzing. - Profiling: profiling while the tests run - hot functions, a call tree and an HTML flame graph.
- Debugging: compile with debug information and set breakpoints, step, and inspect the call stack and
variables, in the IDE or, through the debug adapter
(
ek9 -dap), in VS Code and IntelliJ. - Quality: complexity, naming and the other quality rules are compiler
errors, and every error message has four levels of detail, up to a full explanation with a fix
(
-E0to-E3). - Packaging: dependencies, versioning, signing and deployment
(
-P,-I,-Gk,-D). - AI: an MCP server (
ek9 -mcp) whose tools let an AI assistant compile, query, refactor and edit EK9 source; a Q&A of over 1,200 compiled examples (ek9 -q); and the reference material in Learning with an AI.
Timing
Are you wasting your time reading this? Is a manual premature?
EK9 has been defined and redefined, on paper, from 2010 onwards.
The first grammar was written in 2018 and has changed enormously since. But the language has now found itself: a few decisions constrain, enable, restrict and link its concepts together, like DNA. Getting there took many steps, some of them retrograde, to find balance.
Many features were tried, removed, reintroduced and sometimes removed again, usually because they created a mismatch or contradiction, even when they were very desirable. This manual was first written to define what the language is (and is not): a concrete target of what counts as an EK9 source file. The language is now defined and implemented; what remains before a beta is breadth (the standard library, the native back end) and testing it with real users.
Documentation, support, code examples and community decide adoption more than the nature of the language does. The hope is that you find EK9 compelling, elegant, succinct and fun, but it is communities that create robust applications and drive widespread adoption. With so many good languages developed in the last decade (and more, like Ecstasy, in development), the language, runtime and tooling have to be very, very good.
I have benefited throughout my working life from a wide range of programming languages, tools and operating systems: Pascal, C, C++, ADA, Java, Python, Javascript, KSH, CSH, BASH and Unix, many design patterns and many APIs and frameworks. Each had clear advantages over the others in specific areas.
But some good ideas had become too complex, ran out of steam, had special cases, or forced strange sequences of punctuation that 'jarred'. EK9 is my attempt to forge several ideas together into a cohesive programming language.
Some developers will find the whole approach 'wrong minded'; for some it will be too big, and for others the layout and syntax won't feel right. Bjarne Stroustrup has some comments on languages worth reading, and there are other languages if EK9 does not feel right for you.
Licensing
The EK9 compiler and tools are proprietary; the EK9 runtime is licensed under the Apache License 2.0. Both are copyrighted by Stephen John Limb. The text of this site, every example on it and the Q&A corpus are dedicated to the public domain under CC0 1.0. See the license for details.
The EK9 language has a trade mark. It decides what counts as a valid EK9 source file, which has to be controlled for compatibility; see the trade mark policy.
For licensing inquiries, contact ek9lang@gmail.com.
EK9 language
EK9 is a compiled, strongly typed language (there wasn't an EK1...EK8 and there won't be an EK10).
New Starters
Start with the section on structure. EK9 has the normal constructs and concepts of most imperative languages, including the basics of variable declarations and code comments.
Then read about the built-in types (Integers, Strings, etc) and the operators; together they let you do basic processing. EK9 has several kinds of operator, including a 'fuzzy match'.
Flow control covers if/else, for/do/while and switch. They are like most languages, with additions: guards, assign-if-unset and multiple values in a switch case. Exceptions and error handling is also like other languages with Exceptions, but EK9 has the idea of unset (a bit like an Optional) built into every type.
If you have used Pascal, C, Java or C#, skim these sections (noting the additions) and move on to Records, Classes, Functions and Traits. The concepts exist in most OO languages, even where the syntax differs. First class functions may be new; they turned out to be essential to other capabilities, such as pipeline processing.
If the layout and approach fit the way you like to work, EK9 has much more in the same vein. If they don't, look at other languages instead.
Learning with an AI
An AI assistant makes a good study partner: ask it to explain a construct, to write a small example that you then compile and change, or to tell you what an error means. EK9 suits this way of learning. Whatever the AI writes still has to get past the compiler, so a mistake in its code shows up as an error you can both read, not as a program that quietly does the wrong thing.
Most AI models have seen little EK9, so give yours the reference first:
- llms.txt, an index of this site for language models, and llms-full.txt, the whole reference in one file.
- forAI.json (readable as forAI.html): the features EK9 leaves out and the error each one gives, the APIs of the built-in types, and worked examples.
ek9 -h -E5: the same reference from your installed compiler, with a summary of EK9's main design decisions and its command-line options. Paste it into the conversation, or let an assistant that can run commands run it.- The Q&A (also
ek9 -q <words>) and the data files: compiled questions and answers, and the site's examples, as pages and as data. - The error index: every error code, what it means and a compiled example of the
fix (
ek9 -h E08030prints one).
An assistant that supports the Model Context Protocol (MCP) can go further. Started with ek9 -mcp,
the compiler gives it tools to compile your code, read the errors, explain an error code and search the Q&A,
so it works on your files under the same checks you do. The set-up is in
the MCP server section.
Experienced Developers
Functions and classes are on an equal footing: both were built in from the outset and they complement each other.
There is no null in EK9. It has only Objects, no primitives, and any value can be unset: it has no valid value. For example, a Float divided by zero is unset, and any operation on that unset value gives an unset result. EK9 has operators designed to work with unset values.
There are new ideas that may fill a gap in how you have solved problems before. Extension by composition pulls an architectural design approach into the language. It changes how you build reusable elements: you compose them rather than rely only on inheritance, which allows much more reuse.
EK9 has extension through inheritance, and you can extend functions the same way: you can write an abstract function. With dynamic classes and dynamic functions you can define something similar to (but not the same as) a lambda.
The sections on readable pipelines and pipelines if lambdas were used explain why pure lambdas are not in EK9. That is by design, and some developers will miss them.
Functions can be passed around as variables (delegates, as Microsoft calls them). Combined with classes, this varies behaviour per object instance: a method can delegate to a function, so to change what a class does you do not always need to extend it - give it a different delegate. Together with traits this gives much more flexibility through extension by composition.
Whetting your appetite for more
The capabilities above are similar to other languages; the areas below start to become EK9 specific.
The functional idea of pure (immutability, no side effects) is built in. Pure scopes are limited in assignments and mutations of data, so EK9 needs no explicit immutable data structures and no final, const or similar keywords.
For example, the + operator must be declared pure (E07500): it must not mutate. The += operator is not pure and does mutate state.
EK9 has Generic/Template development (see this article if the terms are new). There are limits on how generic types are defined and used: pragmatic restrictions to limit complexity. Some developers will find them too limiting; for most they give capable constructs for code that works with a range of types in a parametric polymorphic way.
EK9 comes with generic types that were designed in from the outset, not added later - the language needs them. They include:
- Optional - either a single Object held, or empty
- Result - two types of Object held: the OK value and any Error value
- List - a list of objects that are all the same type
- Dict/DictEntry - like Map or Dictionary in other languages
- Iterator - normally used with one of the above, to get each element in turn
- PriorityQueue - a queue kept in priority order, which can be limited to a size
Folding design patterns into the language
Some developers and language theorists argue that a language should be minimal: more keywords and concepts mean more to learn, and patterns come in and out of fashion. Lisp and Clojure take that route, and there is much to be said for it. EK9 goes against it, in a controlled way.
None of the patterns is compulsory. If you write web services, you have to know the HTTP verbs anyway, so having them in the language means you don't also learn a third-party API. If you don't write web services, you never need the verbs at all. Design note: folding design patterns into the language sets out the criticism and the reasoning in full.
Dispatcher
The next ideas pull design patterns into the language. Some exist as third-party APIs or techniques; Swift includes the following dispatch idea.
A dispatcher method on a class handles polymorphic types. You mark one method as a dispatcher that accepts a base type or trait, then write methods of the same name that accept classes extending that base type. When you call the dispatcher, EK9 works out which method is the best match for the argument.
This removes the need for casting or instance of checks. EK9 has no syntax to cast or test the type of an Object, and no word null; other mechanisms make them unnecessary.
Why EK9 does it this way. Without the dispatcher, EK9 would have been forced to introduce casting and instance of. Without operator overloading and ? (is set), it would have to use null.
Dependency Injection (DI) and Inversion of Control (IOC)
If you use Spring but feel it has gone too far from its original idea of IOC and Dependency Injection, EK9 has a more limited, constrained approach built in: components/applications and dependency injection.
This is the main reason EK9 has no static or global variables: with IOC/DI neither is needed. You can still reach a singleton through DI, but it is more loosely bound. DI also complements the constants construct, letting more complex Object instances be used throughout your software.
EK9 also has Aspect Oriented Programming (AOP), designed to dovetail with dependency injection rather than be a separate tool kit. It lets you intercept method calls on a component and do processing before and after the method call.
None of these concepts is new; they exist in different languages, APIs and toolkits. EK9 brings them together with a simple syntax and compiles them. With IOC/DI, variable scope is simpler: there are no global variables fixed in their naming. You can still have a global variable if you have to, but only through IOC.
Why EK9 does it this way. The IOC and aspect-oriented features are deliberately watered down and constrained in where they apply, to prevent overuse. If that is too minimal for you, Java or Kotlin with Spring may suit you better than EK9.
Defaulting Constructors and Operators
The Java project Lombok uses Annotations to generate methods automatically. Both 'Spring' and 'Lombok' are, in effect, an additional programming language on top of Java, written in Annotations.
Generating 'default constructors' and methods like 'toString' or 'hashCode' from a class's fields is a reasonable idea. Lots of getters and setters are not: the class starts to look like a DTO.
EK9 has a simple syntax for a 'default constructor' and for generating simple operators, but no generated getters and setters. Use a record to hold DTO values and compose it with the class, or write the getters and setters yourself if you want them.
EK9 has named arguments, so Object construction is clearer, but every argument must be supplied (unlike Python). You can have several constructors; if you have many parameters, review your design, or group related data in records (better still, use composition with several constructs).
A drunkard's walk?
You may be thinking: design patterns, no null, strange syntax and very specific constructs where a class would do - is this a drunkard's walk, picking up this and that in an incoherent, haphazard way, with every 'buzz word', technique and paradigm in it?
Most large-scale software uses many of these techniques in some form, but always inconsistently, or with so many dependencies that fusing them takes real work. EK9 is a tool box: if you don't need a tool, don't use it.
New/revisited concepts
The ideas that follow rework existing concepts that are often found not in programming languages but in Unix/Linux shells or in third-party components and APIs.
Pipeline processing
Many OO APIs chain methods to filter, map and reduce, in what is called method chaining or a fluent interface. Both are similar in concept to the pipeline Douglas McIlroy created for processing text files on Unix (a very clever idea). So streaming Objects and piping file contents will be familiar to many developers.
EK9 combines both with the functional programming style and the Monad concept from Haskell. A stream of Objects (and Function delegates) is sent through a processing pipeline, where commands (listed below), some with a function, control the flow or transform the contents or type of the Objects that continue down the pipeline.
The pipe '|' symbol joins the parts of the pipeline, as in Unix. The stages include:
- filter/reject - should item continue along the pipeline?
- map - transform item into an item of a different type.
- sort - order all items with a function and pass along the pipeline.
- group - use a function to group items.
- join - accept two items and join them returning a new item of the same type.
- split - use a function to decide if input has to be split.
- uniq - ensure all items passing through are unique.
- tee - take a side copy of the pipeline into another terminal node.
- head - limit the output to first 'n' items.
- tail - limit the output to the last 'n' items.
EK9 adds:
- skip - ignore the first 'n' items and then output all the rest.
- flatten - if the item is some type of collection, this streams out each item in that collection.
- call - if the item in the pipeline is a function - it is called.
- async - if the item in the pipeline is a function it is called asynchronously.
The terminal part of a pipeline collects the results. If you have used shell commands like 'ps -fe | grep mysql', you will feel at home with EK9 pipelines.
Streams/Pipelines explains them all; you may find them one of the main reasons to consider EK9. They remove the need for complex nested for loops over collections (though you can still write those). They present the processing readably, and they reuse functions that have a single, testable (and pure) purpose. Above all they remove endless null checking (an idea from Haskell): EK9 uses is set instead; see operators.
Why EK9 does it this way. The Unix pipeline has stayed in use since the 1970s, so the concept has proven its worth; its syntax is familiar, readable and terse. Functional programming is accepted again (for some it never went away). And in the object-oriented world, the many different methods each API offers for the same streaming leads to confusion, repetition and inconsistency.
This is an important point, and many may disagree: an object-oriented technique has been misused to create a functional programming flow. It is like using a screwdriver as a chisel; you can, but why not use a chisel if that is what's needed?
Pulling pipelines into the language, with the is set nature of Objects (Haskell's Maybe/Monad) and Generics (containers of zero or more Objects), gives one standard syntax and promotes functions as solid, testable building blocks. Neither the functional nor the Object-Oriented approach is claimed to be better; each has strengths and weaknesses, and EK9 lets you use both in harmony.
Threads
Does EK9 use Threads or Cooperative multi-tasking (like Javascript)? EK9 assumes Threaded implementations, and the pipeline above has async, which calls several functions at the same time.
Threads appear elsewhere too: in Network Types the TCP server, and to some degree the UDP server, accept multiple concurrent clients, and signal handling implies multi-threading. For that, EK9 has a Mutex Lock built in. Where you can, avoid simultaneous access to data structures: it leads to complexity, defects and performance issues.
There are no promises or await keywords. If you want to run several tasks at once, stream pipelines and async are the only way.
Without Objects and Generics there could be no Lists, and without Lists, Functions and is set there could be no Pipelines. Each construct matters, and each is used where it fits.
Preview
Here is a preview of how EK9 forges Unix pipelines with functional programming, Monads and an object-oriented approach. cat starts streaming a collection, people is a List of Person (Person is a record), lastNameAndDob, lastNameComparison and the rest are functions, and stdout is an instance of the Stdout class, which prints Strings to the terminal.
Readable pipeline
cat people | uniq by lastNameAndDob | sort with lastNameComparison | group by personProfession | sort by professionComparison | map with personToString > stdout
Full source: introduction_pipeline.ek9 - Download introduction_pipeline.ek9
The flow of the pipeline is obvious. For longer pipelines there is a column layout that is easier still to read:
cat people | uniq by lastNameAndDob | sort with lastNameComparison | group by personProfession | sort by professionComparison | map with personToString > stdout
Full source: introduction_pipeline.ek9 - Download introduction_pipeline.ek9
Readability and testability are the main reasons EK9 does not promote in-line lambdas. The column
layout, with short lines, is always easier to read, at the cost of longer methods and functions.
Here is lastNameAndDob (the backtick String builds the person's last name and date of birth - each
${...} inserts a value as text):
'lastNameAndDob' function implementation
lastNameAndDob() as pure -> p as Person <- rtn as String: `${p.last} ${p.dob}`
Full source: introduction_pipeline.ek9 - Download introduction_pipeline.ek9
With in-line lambdas in the pipeline, you would have to name the variable being accepted (here 'p') and add a new operator '=>'. An excerpt would then read:
Obfuscated pipeline (not supported)
cat people | uniq by (p) => $ p.last + " " + $ p.dob | sort with lastNameComparison | group by personProfession | sort by professionComparison | map with personToString > stdout
You are drawn into the lastNameAndDob implementation, and it distracts you from the flow of the pipeline: you lose your place in what the pipeline is for. Imagine the full pipeline with every implementation detail exposed, some over several lines: much harder to understand, test, maintain and alter.
Why EK9 does it this way. When every operation has to be a function, writing a unit test for it is straightforward. An in-line lambda cannot be tested in isolation and cannot be reused, which leads to copy-paste and code that is not DRY. Generic collections, record/class objects and functions each do what they are best at, and the result is readable, re-usable, simple, maintainable and strongly typed. Writing functions instead of in-line lambdas costs more; that cost is acknowledged and accepted.
Web Services
EK9 has support built into the language to define web services and resources, i.e. HTTP end points. These capabilities usually come from third-party APIs with bespoke Annotations, but HTTP and REST/RESTful web services are here to stay. For REST, read the original dissertation by Roy Thomas Fielding (Dr). The same web service methods can implement GraphQL.
Technologies that were once an aside to mainstream programming are now a fundamental building block, and EK9 treats them as one. Below is a simple GET; Web Services shows all the verbs. Read operators first: POST is +=, 'adding' a resource, in keeping with how EK9 treats collections.
You can go beyond CRUD (RPC) and implement level 3 REST with HATEOAS, which is driven by the content. EK9 provides the basics: HTTP and its verbs.
#!ek9 defines module site.introduction.addresses defines service //define the service called 'Addresses', map it to a URI of '/addresses' and open it to callers Addresses for :/addresses open //A GET listByPostCode() as GET for :/{postCode} -> postCode as String :=: PATH "postCode" acceptLanguage as String :=: HEADER "Accept-Language" <- response as HTTPResponse: (postCode, acceptLanguage) with trait of HTTPResponse //override HTTPResponse content, status and so on to complete processing. override content() <- rtn as Bytes: Bytes().withUtf8(`{"postCode": "${postCode}"}`) override contentType() as pure <- rtn as String: "application/json" override contentLanguage() as pure <- rtn as String: acceptLanguage override status() as pure <- rtn as Integer: 200 default operator ?
The key concept in REST is the resource. In EK9 it can be any content, serialised from any of the constructs - typically records and/or classes, as they hold state. A String in a particular layout (i.e. JSON) works too, if that is appropriate.
EK9 philosophy (or approach)
EK9 blends existing language constructs, concepts, design patterns and sometimes syntax, gently and cohesively. So an individual EK9 construct is not always as capable, terse or flexible as its counterpart in another language.
Linked together, though, the approaches give a solution that is more explicit, simpler to develop, more capable overall and reusable, and above all readable. The focus is code that is reasonably terse, understandable and reusable, rather than quick to write the first time. Long-lived code is always read many more times than it is written.
EK9 is statically, strongly typed, and favours readability, reuse and composition over terseness. For that reason there are no list comprehensions (E01081); pipelines deliver the same functionality.
Lambdas
EK9 separates how functionality is implemented from where and how it is used (abstraction). Including the details of a lambda where it is used flies in the face of that; compare the function pipeline with the in-line lambda pipeline. A simple operation like the proposed lambda is likely to be needed elsewhere, so define a function and reuse it; a more complex one has even more reason to be a named, testable function.
There is a compromise: a dynamic function that extends an abstract function can be written in-line, but only as a single line. See DynamicMathExample1 and DynamicMathExample2 for why. Because abstract functions infer the incoming and returning parameters, the main cost of declaring them is mitigated. Only by writing EK9 code and the worked examples did it become obvious that this compromise should be made. Designing a language is all about compromises; in-line lambdas will be missed by some developers.
Break and Return
EK9 does not use break or return for flow control. Functions and Methods return values, but always at the end of the scope block, and a loop cannot be broken out of early. Like most things in software design, this is subject to heated debate.
Why EK9 does it this way. There are already several ways to control flow: if/else if/else, switch and try/catch/finally with exceptions. The wish to 'return' early or 'break' out of a loop comes from a procedural, imperative approach. Its biggest cost is how hard it becomes for a human (not a compiler) to follow the path through a block. Logic near the end of a function or method may no longer run, so the code is harder to reason about, refactor and modify. The design note on break, continue and return gives the record behind the decision.
Break and Return Alternative
Instead of many for loops and if/else branches, use pipeline processing: combinations of filter, head, tail and skip do what early returns and breaks did, in a standard, maintainable and obvious syntax. You may find it constraining at first. It leads to more small, discrete functions, which promotes re-use and unit testing, and each function name (ideally a meaningful one) documents what it does.
User Interface, SQL and Libraries
EK9 leaves three things outside the language:
- User interfaces. Front ends stay in the frameworks you already use and connect to an EK9 microservice through Web Services (REST), or blend in as micro front-ends.
- SQL and persistence. There is no hibernate layer: use SQL/NoSQL in the dialect of your choice. An EK9 record can be a database row, but it does not have to be; it can aggregate data from anywhere.
- Service discovery and configuration. EK9 does not define how a microservice announces itself or is configured; that belongs to deployment, and EK9 is not prescriptive about it.
Design note: what EK9 leaves out explains each decision.
Conclusion
EK9 was developed over many years by looking at which techniques and technologies work and which don't. The hope is that its syntax, mechanisms and layered approach give you a range of tools for software that is flexible, pragmatic, maintainable and clear.
Some developers with similar experience will have drawn the same conclusions; some will strongly disagree with the direction EK9 has taken. There is ample choice: select the language that suits your experience, your nature and your project.
Many larger applications are now microservices, some running in containers behind caches, load-balancers and façades.
Finally
EK9 is big, and in some ways a departure from existing languages. To be widely adopted it has to give real, significant benefits over other languages; "a bit easier or better here and there" is not enough.
Languages usually become popular not through syntax but by solving a major development problem or finding a niche. It is not yet clear how EK9 will. Its syntax and constructs give developers ways to express themselves, but that is unlikely to be enough on its own: it needs a compelling set of APIs that give developers value.
The layout has some general aspects:
- White space groups and focuses the code
- Most code sits in the first 80 to 120 characters from the left-hand side of the source
- Indentation makes it obvious where the code flow will go
- It is not textually dense: there is lots of white space
- Exceptions are the only early way out of a code block
- The -> shows data flowing 'into' something
- The <- shows data flowing 'out of' something
- Punctuation is mainly for 'operators', not blocks of code
It may take a while to get your 'eye in'. Once you are used to the indentation and to -> and <-, the flow of data should visually 'click'. You should be able to 'step back' and see the structure without reading the words - the shape of the wood rather than each tree. It sounds a bit hippy and new-age, but the syntax and file layout were designed to be pleasing on the eye and readable for humans (indentation, columns, structure and direction).
Operators get particular focus because they carry consistent meaning taught in schools from an early age, such as +/-. They solve many of the naming and semantic problems of languages without operators.
Next Steps
Go to structure for much more detail on the constructs outlined above.
Other Languages
Other languages may suit specific programming tasks better. Before looking into EK9, consider what kind of solution you want to build.
Compiled to produce native applications
- C - Suitable for low level programming device drivers/operating systems with good performance
- C++ - Similar to C but Object-Oriented in nature, a steep learning curve but very capable.
- Go (golang) - A modern language with C like aspects; with built-in memory management, again high performance
- Rust - Similar in some ways to C/C++ and Go but with more of a focus on concurrent programming
Compiled and strongly typed languages that use a runtime
- Java - A good Object-Oriented programming language, limited in some ways in its Functional/Generic aspects.
- Scala - Builds on top of Java and provides more of a functional programming approach with a more concise syntax.
- Kotlin - Similar (though not in syntax) to Scala; Kotlin extends Java and can re-use many Java packages
- Dart - A cross development platform with a very compelling 'Flutter' UI API.
- C Sharp - A very good Object-Oriented programming language provided by Microsoft
Choosing between Java, Scala and Kotlin (all on the JVM) comes down to preference and balance; there is no big issue with any of them.
C# is a bigger decision: it takes you well into the Microsoft camp. It is a very, very good language and addresses many of the issues that emerged in Java.
Other Compiled and strongly typed languages of note
- Swift - Mainly used by Apple developers but supported on other platforms (linux for example) with a very clean and concise syntax.
Interpreted/Scripted Languages
- JavaScript - Once mainly used in the browser there is now first class server side support through NodeJS
- Python - A truly inspirational language - that has brought simple easy to read syntax and an extensive set of libraries to development.
Other Languages worth considering
- Haskell
- Lisp
Next Steps
To understand more about EK9, read structure next.