Concept Simplification
Why LLLTS removes overlapping language choices and keeps one canonical form when possible.
One canonical form when possible
TypeScript and JavaScript often offer several ways to express nearly the same job. That is useful for humans writing by hand, but it also creates branching: the reader or the model has to choose between overlapping constructs before it can focus on the actual domain problem.
Reject duplicated concepts. Keep one strong default when extra variants mainly add decision load.
That is the simplification idea in LLLTS. The language does not try to remove power for
novelty. It tries to remove duplicate or overloaded choices when one canonical form can still do
the work. The result is lower concept count, lower ambiguity, and more attention available for
user intent, domain modeling, structure, and verification.
Fewer equally plausible constructs means fewer syntax branches to consider. The model can spend more of its limited working attention on the subject of the code instead of on choosing between several near-duplicate language forms.
Structural simplifications
The clearest examples happen at the project and file level. Ordinary TypeScript is comfortable
with free functions, loose constants, extra helper interfaces, and several primary concepts in one
file. LLLTS collapses those options into one main structural path.
const cache = new Map<string, User>();
function loadUser(id: string): User {
return new User(id);
}export class UserRepository {
static readonly cache = new Map<string, User>();
@Spec("Loads a user by id.")
static loadUser(id: string): User {
return new User(id);
}
}
The same idea shows up in type modeling. If you need a pure data contract, prefer a
type. If you need shared runtime behavior or overridable methods, use an abstract
class. The point is not "never express a contract." The point is to avoid carrying interface,
type alias, free function, and loose constant patterns all at once when one canonical choice is
enough.
| Common job | Canonical LLLTS form |
|---|---|
| Primary runtime behavior | class in its own filename-matched file |
| Pure data shape | type |
| Free helper | class method, usually static |
| Shared top-level constant | class property, usually static readonly |
| Bootstrap action | a final top-level new ClassName() only where explicitly allowed |
Expression and guardrail simplifications
Some simplifications are not about duplicate declarations. They are about overloaded meaning.
JavaScript lets conditions, comparisons, arithmetic, and nullability shortcuts carry several
different interpretations. LLLTS narrows those positions to one explicit reading.
if (userName) {
publish();
}
if (status == 0) {
reset();
}
const delta = "5" - 2;
return user!.name;if (userName !== "") {
publish();
}
if (status === 0) {
reset();
}
const parsedCount = Number.parseInt("5", 10);
const delta = parsedCount - 2;
if (user === undefined) {
throw new Error("Expected user");
}
return user.name;
In LLLTS, boolean positions should mean boolean, arithmetic should mean numeric intent,
and nullability claims should be proven in code instead of asserted by punctuation. This keeps the
meaning of those positions stable for both humans and models.
Testing-workflow simplifications
Testing gets the same treatment. Instead of letting projects invent many layouts for tests,
host-selection rules, and scenario signatures, LLLTS keeps one companion model.
import { describe, it, expect } from "vitest";
describe("UserService", () => {
it("loads a user", async () => {
expect(await loadUser("42")).toBeDefined();
});
});import "./UserService.lll";
import { Scenario, Spec, type ScenarioParameter } from "../../public/lll.lll";
@Spec("Exercises UserService scenarios.")
export class UserServiceTest {
testType = "unit" as const;
@Scenario("Loads a user.")
static async loadsUser(scenario: ScenarioParameter): Promise<void> {
scenario.assert(true, "Replace with host-class assertion.");
}
}
This is an intentional narrowing. It gives up some layout freedom so the language, the compiler,
and the model all agree on one discoverable way to find tests, name them, run them, and classify
them as "unit" or "behavioral".
Not every rule is concept reduction
It would be too broad to claim that every `LLLTS` restriction exists to remove duplicates. Some rules are there for local reasoning and verification discipline instead.
@Spec("...")makes purpose visible before the body begins.- Explicit return types make contracts readable from signatures alone.
- File, method, and folder limits keep complexity density bounded.
These rules complement simplification, but they are better described as boundedness and auditability rules rather than as concept elimination.
Rejected simplifications and boundaries
The simplification bar is not "ban syntax because it looks different." The stronger test is: does one canonical form really cover the job without special-case exceptions?
A good example is backticks-only strings. The idea sounds attractive, but static ECMAScript imports still require quoted module specifiers, and TypeScript ambient module declarations do too. That would force exceptions such as:
import "./ClassName.lll";
or
declare module "pkg"
Once exceptions appear, the rule stops simplifying the language. So LLLTS rejects that
idea and keeps delimiter choice aligned with valid underlying TypeScript and JavaScript syntax.
The broader result
The practical claim is modest but important: when the language removes duplicated concepts and narrows overloaded ones, both humans and models have less syntax branching to manage. That leaves more room for the real work of software: naming the right concepts, giving them stable structure, and verifying behavior through scenarios.