Naming things is one of the two hard problems in computer science. The other is cache invalidation. This project has both (see: TypePool interning and dirty tracking), but this document is about the first.
Names in the Organic Assembler are not labels slapped on after the fact. They are decisions — small acts of design that accumulate into the character of the system. A bad name is a lie that everyone agrees to believe. A good name is a truth that makes other truths easier to see.
The project was born as nanolang. The editor was nanoflow. The compiler was
nanoc. The runtime was nanoruntime. The file format was nanoprog@0.
Then the project grew, and "nano" stopped fitting. The language was not nano — it had a rich type system, bidirectional inference, lambda capture, compile-time metaprogramming. The editor was not nano — it had mutation batching, dirty tracking, observer patterns, three-layer architecture. Nothing about the system was small.
The rename to atto happened in a single phase (Phase 2b, March 24). Every file,
every variable, every comment, every format string. nanolang → attolang.
nanoflow → attoflow. nanoc → attoc. nanoprog@0 → attoprog@0.
The version markers tell the story:
nanoprog@0 → nanoprog@1 → attoprog@0 → attoprog@1 → instrument@atto:0
Each migration step is a function in the serializer. The old names are not forgotten —
they live in the migration code, translating the past into the present. The system can
still load a nanoprog@0 file. It just calls it instrument@atto:0 when it saves.
"Atto" means 10⁻¹⁸. It is smaller than "nano" (10⁻⁹) by nine orders of magnitude. The irony is intentional: as the project grew more complex, its name became more humble. The best names have a sense of humor about themselves.
In early commits, connections between nodes were called wires. A wire connected an output pin to an input pin. Simple, physical, intuitive.
Then named connections appeared — the ability to give a name to a signal so that distant nodes could share it without visual routing. These were not wires anymore; they were nets. The word comes from electronics: a net is a set of electrically connected points, regardless of the physical routing.
The rename is visible in the git log:
wire -> net in graph_builder
This single commit changed the vocabulary of the entire builder layer. WireBuilder
became NetBuilder. wire_id became net_name. The old word persisted in the
visual layer — we still draw wires on the canvas — but the semantic layer speaks
of nets.
The lesson: when the concept changes, the name must change with it. A wire implies point-to-point. A net implies broadcast. The old name would have been a lie.
Pin directions were originally named from the perspective of the pin itself:
BangInput (a pin that receives a bang) and BangOutput (a pin that sends a bang).
But this created confusion when reading node definitions. Does "BangInput" mean "the input that carries the bang signal" or "the bang that inputs to this node"? Both readings are valid, and that ambiguity is a naming failure.
The rename to BangTrigger and BangNext resolved the ambiguity by naming what
the pin does:
- BangTrigger — this pin triggers the node's execution
- BangNext — this pin fires next, continuing the bang chain
The new names are verbs, not nouns. They describe action, not identity. This is a pattern throughout the codebase: when a name causes confusion, rename it to describe behavior rather than structure.
A small rename, but it illustrates a principle: naming conventions are not
optional. The C++ convention in this project is snake_case for functions. as_Node
violates that convention. as_node does not. The rename happened in a dedicated
commit with dead code deletion, because naming fixes deserve their own commits.
Another convention fix. Compound names use underscores. graphbuilder looks like
a single word; graph_builder reveals its structure. The underscore is not
punctuation — it is meaning. It says: this is two concepts composed.
The top-level editor class was originally called editor.cpp / editor.h. But it
managed tabs, file browsing, build toolbars, and child processes — it was a window,
not an editor. The actual editing happened in Editor2Pane. The rename aligned
the filename with its responsibility.
| Name | Meaning | Why this name |
|---|---|---|
| Organic Assembler | The project as a whole | "Organic" because instruments grow organically from nodes; "Assembler" because the system assembles them into running programs |
| orgasm | Repository / directory name | The abbreviation. Memorable, irreverent, honest about the creative pleasure of building instruments |
| attolang | The core language library | "atto" (10⁻¹⁸) + "lang" (language). The language is a building block, deliberately small in scope |
| attoflow | The visual editor | "atto" + "flow" (dataflow). You author instruments by directing the flow of data |
| attoc | The compiler | "atto" + "c" (compiler). Following the Unix tradition: cc, gcc, rustc, attoc |
| attohost | The instrument runtime | "atto" + "host" (host process). The runtime hosts the instrument, providing it a world to live in |
| attostd | The standard library | "atto" + "std" (standard). Following the C++/Rust convention of a std module |
| Name | Meaning | Why this name |
|---|---|---|
| TypeExpr | A type expression | Not "Type" (too generic) or "TypeNode" (implies a tree). TypeExpr is a self-contained expression that fully describes a type |
| TypeKind | What a value is | "Kind" in the type-theory sense: the shape of a type. Not to be confused with "kind" in Haskell (types of types) |
| TypeCategory | How a value is accessed | "Category" as in grammatical category. Data, Reference, Iterator, Lambda — these are ways of being, not types of thing |
| TypePool | Intern cache for types | "Pool" as in object pool. Types are interned (deduplicated) so that pointer equality implies structural equality |
| TypeParser | Recursive-descent type parser | Self-documenting. Parses type strings into TypeExpr structures |
| ScalarType | Numeric primitive types | "Scalar" as opposed to "vector" or "composite". u8, s32, f64 are scalars |
| literal<T,V> | Compile-time constant | "Literal" as in "the literal value 42". T is the type domain, V is the value. The angle brackets echo C++ template syntax |
| symbol<name,type> | Named reference | "Symbol" as in symbol table. A name that refers to something, carrying both the name and what it resolves to |
| Name | Meaning | Why this name |
|---|---|---|
| FlowGraph | The top-level program representation | "Flow" because it is a dataflow graph. Not "Program" (too abstract) or "Scene" (too visual) |
| FlowNode | A computation step in the graph | "Node" is the standard graph-theory term. "Flow" prefix distinguishes it from GUI nodes |
| FlowLink | A connection between pins | "Link" rather than "edge" because links carry type information and net names — they are richer than mathematical edges |
| FlowPin | An input or output on a node | "Pin" from electronics: a connection point on a component. Familiar to anyone who has used a visual programming tool |
| GraphBuilder | High-level editing API | "Builder" as in builder pattern — it constructs and modifies graphs with structured operations, not raw mutations |
| GraphIndex | Fast lookup structure | "Index" as in database index — it accelerates queries without changing the underlying data |
| GraphInference | Type inference engine | Self-documenting. Runs inference over a graph |
| Name | Meaning | Why this name |
|---|---|---|
| VisualEditor | Canvas rendering layer | "Visual" because this layer knows only about drawing — coordinates, shapes, colors |
| Editor2Pane | Semantic editing layer | "Editor2" because it is the second-generation editor (replacing editor1). "Pane" because it is one panel in a tabbed window |
| FlowEditorWindow | Top-level application window | "Window" because it manages the OS window, tabs, toolbars, and child processes |
| NodeEditorImpl | Per-node editor state | "Impl" because it implements the INodeEditor interface. Each node in the graph has one |
| NetEditorImpl | Per-net editor state | Same pattern as NodeEditorImpl, but for named nets |
| Name | Meaning | Why this name |
|---|---|---|
| $empty | Node sentinel (no real node) | "Empty" because it has no content. The $ prefix marks it as a system-generated ID |
| $unconnected | Net sentinel (no real net) | "Unconnected" because the pin has no wire. Descriptive of the situation, not the object |
| Name | Meaning | Why this name |
|---|---|---|
| $auto-<hex> | Auto-generated node ID | "$auto" says "the system chose this". The hex GUID ensures uniqueness |
| <node-id>-in0 | Input pin reference | The dash-separated format encodes structure: which node, which pin, which index |
| <node-id>-out0 | Output pin reference | Same convention as input, for outputs |
| <node-id>-bang0 | Bang trigger pin | Same convention, for bang triggers |
| $<name> | Named net | The $ prefix distinguishes net names from node IDs. Short, scannable |
TypeCategory is better than TypeAccessMode. The concept is categorical (Data,
Reference, Iterator, Lambda are categories of being), not modal (they don't "switch
modes"). The name should survive a refactor of the implementation.
FlowGraph is better than NodeArray. The concept is a graph with dataflow semantics.
The implementation happens to use arrays, but that is incidental. If the implementation
changed to use a hash map, FlowGraph would still be correct; NodeArray would be a lie.
mark_dirty()— verb, describes what happensis_dirty()— adjective (via "is"), describes a stateedit_start()/edit_commit()— verbs, describe the operationFlowGraph— noun, names a thingGraphBuilder— noun (agent noun: "one who builds"), names a thing that acts
Do not use verbs for things or nouns for actions. BuildGraph is confusing: is it a
command (build the graph!) or a noun (a built graph)? GraphBuilder is unambiguous.
f32>float32>single_precision_float— everyone knows f32u8>uint8>unsigned_eight_bit_integer— everyone knows u8ffi>foreign_function_interface— everyone knows FFIgui>graphical_user_interface— everyone knows GUIexpr>expression— used so often that brevity mattersdecl>declaration— same reason
But:
FlowGraph>FG— the abbreviation is meaninglessTypeCategory>TC— the abbreviation is meaninglessGraphBuilder>GB— the abbreviation is meaningless
The test: if someone seeing the abbreviation for the first time cannot guess its meaning, spell it out.
Flowprefix: FlowGraph, FlowNode, FlowLink, FlowPin — all part of the flow modelArgprefix: ArgNet2, ArgNumber2, ArgString2, ArgExpr2 — all argument typesNodeprefix: NodeTypeID, NodeType, NodeEditorImpl — all node-relatedTypeprefix: TypeExpr, TypeKind, TypeCategory, TypePool, TypeParser — all type-related2suffix: FlowArg2, Editor2Pane, node_types2.h — second generation (replaces v1)Ptrsuffix: TypePtr, ExprPtr, FlowArg2Ptr — smart pointer aliasesBangsuffix: StoreBang, CallBang, ExprBang — bang-chain variants of base nodesImplsuffix: NodeEditorImpl, NetEditorImpl — interface implementations
Prefixes group by domain. Suffixes classify within a domain. Together they create a two-dimensional naming grid that is scannable and predictable.
Names that describe what something is are more stable than names that describe where it lives or when it was created.
GraphBuilderwill still make sense if the file moves fromsrc/atto/tosrc/core/Editor2Panewill stop making sense when Editor3 arrives (but by then it will be renamed)FlowGraphwill still make sense if the serialization format changesnanoprog@0stopped making sense when the language was renamed (and was migrated)
When choosing a name, ask: "Will this name still be true after the next refactor?"
The type category sigils are single-character names:
| Sigil | Name | Mnemonic |
|---|---|---|
% |
Data | Percent — the "default" (as in "100% of values are data") |
& |
Reference | Ampersand — borrowed from C/C++ reference syntax |
^ |
Iterator | Caret — points "up" to the current position |
@ |
Lambda | At — a function "at" a callable address |
# |
Enum | Hash — enumerated, numbered items |
! |
Bang | Exclamation — an imperative command, "do this!" |
~ |
Event | Tilde — a wave, something that comes and goes |
Each sigil was chosen because it has a visual or linguistic mnemonic. ! for bang is
the most natural: an exclamation mark is a bang. ~ for event evokes a signal wave.
& for reference comes from C++ and needs no explanation.
Sigils are the shortest possible names. They work because the set is small (seven), the context is constrained (type strings), and the mnemonics are strong. Do not add new sigils without strong justification — the cognitive load scales faster than the count.
Error messages in the Organic Assembler follow a convention:
- Lowercase
- Descriptive
- Context-included
"type mismatch: expected f32, got u8"
"undefined symbol: foo"
"cannot iterate over non-collection type f32"
Each error message names the problem in a way that suggests the fix. "type mismatch: expected f32, got u8" tells you exactly which types conflicted and which direction the expectation flows. "undefined symbol: foo" tells you which name is missing. The error message is a name for a specific situation, and like all names, it should be precise, honest, and helpful.
The file organization of the codebase is itself a naming system:
src/atto/ — core language (no external deps)
src/attoc/ — compiler (depends on atto)
src/attoflow/ — editor (depends on atto, SDL3, ImGui)
src/attoruntime/ — runtime (depends on atto)
attostd/ — standard library (.atto files)
scenes/ — example instruments
tests/ — automated tests
docs/ — documentation
Each directory name is a boundary declaration. src/atto/ says: "everything in here
has zero external dependencies." src/attoflow/ says: "everything in here can use
SDL3 and ImGui." The directory name is not just organization — it is a constraint
that the build system enforces.
When you create a new file, its location is its first name. Choose carefully: putting
a file in src/atto/ is a promise that it will never depend on SDL3. Putting it in
src/attoflow/ is a promise that it is editor-specific.
Every variable, function, type, file, commit message, and error message is a naming
moment. Most naming moments are small and forgettable — int i, auto& node, bool ok.
These are fine. Not every name needs to be profound.
But some naming moments are load-bearing. The name FlowGraph shapes how everyone
thinks about the data model. The name instrument shapes how everyone thinks about
the programs. The name bang shapes how everyone thinks about execution order. These
names, once established, become the vocabulary of the project. Changing them requires
a Great Rename.
Recognize the load-bearing naming moments. Spend time on them. Sleep on them. The name you choose today will be repeated ten thousand times in code, docs, conversations, and commit messages. It will shape how people think about the concept it names.
Rename when:
- The name is actively misleading (wire → net, when connections became broadcast)
- The name violates an established convention (as_Node → as_node)
- The concept has genuinely changed (editor → window, when the class became a window manager)
Do not rename when:
- You merely prefer a different name (preference is not justification)
- The name is "not perfect but fine" (perfection is the enemy of stability)
- The rename would touch 50+ files for marginal clarity (blast radius matters)
A rename is a coordinated migration. Do it in one commit, touch all references, update docs and comments. Half-renamed code is worse than badly-named code — at least badly-named code is consistent.
When a name becomes a compound (GraphBuilderEntry, FlowNodeBuilderPtr,
INodeEditorImpl), check if the compounds are load-bearing:
GraphBuilder+Entry= a thing in the graph builder. Good: both parts carry meaning.FlowNodeBuilder+Ptr= a pointer to a flow node builder. Good:Ptris a standard suffix.I+NodeEditor+Impl= an implementation of the node editor interface. Good:Iprefix for interfaces,Implsuffix for implementations.
But beware compound names that merely concatenate context:
EditorGraphBuilderNodeArgNet2Ptr— too long, too many levels of nesting- If a name requires more than three components, the concept may need decomposition
The most important name in the Organic Assembler is instrument. Not "program." Not "project." Not "sketch" or "patch" or "scene."
"Instrument" carries specific connotations:
- A musical instrument is played, not executed
- A scientific instrument measures, not computes
- An instrument has a purpose — it does one thing well
- An instrument is physical — it exists in the world, not just in memory
- An instrument is expressive — the same instrument in different hands produces different results
Every .atto file is an instrument. The system is an operating system for instruments.
This name frames the entire project. It tells the user: you are not writing code, you
are building an instrument. Treat it with the care and intentionality that word implies.
The name is the first line of documentation. It is the shortest possible explanation. It is the thing that makes everything else easier to understand — or harder, if chosen poorly. Name things right, and the code explains itself. Name things wrong, and no amount of documentation can save you.