Class 2 · Inside the Language
4 h · 2 at the keyboard 00:00A programming language is not a way of talking to a computer. It is a way of writing down a decision so precisely that a machine can carry it out without you in the room.
Logic gym#
Fifteen minutes, on paper, in pairs. Same rule as Class 1: say the answer out loud, in a full sentence, before you check it. A vague guess that could be retrospectively right is the habit this gym exists to break.
1. What does it print#
a = "12"
b = 3
print(a * b)
Say what happens in Python. Then say what happens if you write the same three lines in JavaScript. They differ, and neither is a mistake by the language. Say which one you would rather have when the value came out of a spreadsheet, and why.
2. Two names, one thing#
a = [1, 2, 3]
b = a
b.append(4)
What is in a? Now draw it: two boxes with names in them, one box with a list in it, and arrows. The drawing is the answer. Anybody who can draw this correctly will never be surprised by this class of bug again, and anybody who cannot will be surprised by it for years.
3. The comparison that is false#
0.1 + 0.2 == 0.3 is false. Before anybody explains why, answer a different question: name three other places in a show where two numbers that should be equal will not be. A timecode. A fader. A measured distance. What do all three have in common?
4. Count it out loud#
Fixtures at eight channels each, starting at address 1.
- Which fixture is on 57?
- What is the last address used by fixture 12?
- How many fit before the universe runs out, and what is the address of the first one that does not?
Say all four before anybody opens anything. Then say what changes if the start address is 101.
5. Read the signature, judge the function#
def patch(fixtures, start, footprint, universe=1, skip=None, force=False):
You have not seen the body. From the signature alone: what does this probably do, what are the two parameters you would ask about before calling it, and which one would you refuse to use until somebody explained it?
That refusal is a professional skill. A function whose behaviour you cannot predict from its signature is one you should not be calling near an audience.
What the gym is for#
Every item is the same move: hold the state, run it forward, commit. Class 1 did it with behaviour. This class does it with values, and the difference is that here you also have to hold what type each thing is, which is the part that catches people for about a fortnight and then never again.
What happens between typing and running#
You write text. Text does nothing. Something has to turn it into the fetch-arithmetic-store-decide loop from Class 1. Four steps, in every language that has ever existed.
1. Lexing. The text is chopped into tokens. level = level + 5 becomes: name, equals, name, plus, number. Spaces disappear. This is why indentation matters in Python and not in C: Python's lexer emits tokens for it and C's throws it away.
2. Parsing. The tokens are assembled into a tree that carries the structure. 2 + 3 * 4 becomes a tree where the multiply hangs below the add, which is precisely why the answer is 14 and not 20. A syntax error is this step failing: the parser reached a token that cannot follow the last one and has no idea what you meant.
3. Compiling. The tree becomes instructions. This is where languages split:
- Compiled ahead of time, like C or C++ or Rust: the whole program becomes machine code for one kind of processor, before it ever runs. Fast, and you must build it again for a different chip.
- Compiled to bytecode, like Python, Java or C#: instructions for a pretend machine, which a runtime then executes. Portable, slower, and it can tell you a great deal when it goes wrong.
- Just in time, like JavaScript in a browser or in Node: starts interpreting, notices which parts run often, and compiles those to machine code while running. This is why the first pass through an animation is slower than the hundredth.
What happens between typing and running#
4. Executing. The instructions run, on a real CPU, one at a time, very fast.
Why a technical director cares about step 3#
Because it decides what you can do at 23:40.
A compiled language means somebody must rebuild the firmware and reflash the device, which needs their toolchain, their source, and their availability. An interpreted language means the source is on the machine, and can be read and changed in place with a text editor.
That is the practical difference between "the supplier can fix it on Tuesday" and "we can look right now". It is worth asking, in procurement, before it is 23:40.
Why a technical director cares about step 3#
| Language | Kind | Where you meet it in a show |
|---|---|---|
| C / C++ | Compiled ahead of time | Arduino sketches, console firmware, media server internals |
| Python | Bytecode, interpreted | Automation scripts, Raspberry Pi show control, data wrangling |
| JavaScript / TypeScript | JIT compiled | Browser panels, Node tools, Remotion, Companion modules |
| Lua | Bytecode, interpreted | Embedded scripting in consoles and media servers |
| C# | Bytecode, JIT | Windows show tools, Unity-based projection work |
Values, and what a type is for#
Everything a program handles is a value: a number, a piece of text, a truth, a collection. A type is the answer to "what can I do with this, and what does it mean?"
The important types, and the show-floor version of each:
Values, and what a type is for#
| Type | What it holds | Where it bites |
|---|---|---|
| Integer | A whole number, exactly | Channel 512, frame 1440, universe 3 |
| Float | A number with a fractional part, approximately | Fade times, positions, dB, anything continuous |
| String | Text, in a specific encoding | "512" is not 512, and everything from a file is text first |
| Boolean | True or false | Is the door open, is the fixture patched |
| List / array | An ordered collection | Cue stack, channel list, frames of an animation |
| Dictionary / map | Look something up by a key | Fixture by address, cue by number |
| None / null | The absence of a value | The return value of a lookup that found nothing |
The one that costs the most time in production work is the third row. Everything that arrives from a file, a network message, a form or a serial port arrives as text. A DMX address read from a CSV is "512", four characters, and "512" + 1 is either an error or "5121" depending on the language. Neither is what you wanted.
Values, and what a type is for#
addr = "512" # from a CSV
print(addr + 1) # TypeError in Python
print(int(addr) + 1) # 513, which is what you meantJavaScript will not error. It will helpfully produce "5121", and your patch will be wrong two hundred rows later with nothing having gone visibly wrong.
Static and dynamic, and which one you want at 23:40#
A statically typed language checks types before running. int addr = "512"; will not compile in C, and the error arrives on your laptop, in daylight, weeks before the show.
A dynamically typed language checks while running. Python discovers the problem the moment it reaches that line, which might be during the show, on the one branch nobody tested.
Neither is better in general. But there is a rule that holds up in this industry:
The more a piece of code runs unattended in front of an audience, the more you want its mistakes to happen before it ships. Firmware for a prop: static. A script you run yourself on the get-in: dynamic is fine, because you are standing there.
Do it now: everything arrives as text#
Seven cases, and only the first is the one you would have written. The other six are a spreadsheet that went through three people, which is every patch you will ever be handed.
Notice what the last case is defending against. Returning NaN for "twelve" does not fail; it succeeds with a value that poisons everything downstream, and the fault surfaces four functions later in code that is not wrong. Convert at the edge, once, and refuse what will not convert.
References, and the bug where two names are one thing#
Here is the surprise that catches everybody once, and catches some people annually.
default_cue = {"level": 0, "time": 3.0}
cue_1 = default_cue
cue_2 = default_cue
cue_1["level"] = 255
print(cue_2["level"]) # 255. Not 0.cue_2 was never touched. It reads 255 anyway.
The reason: a variable does not contain the dictionary. It contains a reference to it, an arrow pointing at one object in memory. Three names, one object. Change it through any of them and all three see it, because there is only one thing there.
The same code with numbers behaves the way you expected, because assigning a number copies the value. So the rule is not intuitive and cannot be guessed:
References, and the bug where two names are one thing#
- Simple values (numbers, booleans, and in most languages text) behave as copies.
- Collections (lists, dictionaries, objects) behave as references.
In production code this appears as: every fixture in the rig getting the same values, because they were all built from one template that was never copied. Or an "undo" that does not undo, because the snapshot you saved was a reference to the thing you then changed. The symptom is always the same and worth memorising: something you did not touch changed.
The cure is to copy explicitly when you mean a copy:
import copy
cue_1 = copy.deepcopy(default_cue) # now it is genuinely its own dictionaryDo it now: two names, one object#
This is the most famous bug in Python and it exists, in a different costume, in this language too. The exercise gives you a broken makeCue and four cases. Three of them pass while it is broken, which is the actual lesson: the test that catches this is the one that makes two of the thing, and "make two of it" belongs in your head as a reflex.
Floating point, and the 0.1 + 0.2 problem#
Floating point, and the 0.1 + 0.2 problem#
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
FalseThis is not a bug in Python. Every language on every machine you will ever touch does this, because they all use the same IEEE 754 standard, and it follows from something simple.
A float is stored in binary. In binary, one tenth is a recurring fraction, the way one third is in decimal: 0.0001100110011... forever. The machine has 53 bits of significand, so it stores the closest value it can and stops. The error is about one part in 10¹⁶, and adding two of them makes it visible.
The consequences that reach a show:
Never compare floats with equals. if position == 12.0 may be false when the position is 12.000000000000002. Compare with a tolerance instead:
Floating point, and the 0.1 + 0.2 problem#
if abs(position - 12.0) < 0.001:Do not accumulate. Adding 0.1 a hundred times does not give 10.0, it gives 9.99999999999998, because a hundred small errors add up. Multiply instead: t = i * 0.1. This one matters for animation, where a frame time added repeatedly drifts and a frame number multiplied does not. Class 6 is built on that fact.
Use integers for money, addresses and frames. Anything counted rather than measured should be an integer. Frame 1440 is exact. 57.6 seconds at 25 fps is not.
Where it does not matter. A fade time of 3.0000000000000004 seconds is 0.4 femtoseconds off, and your fixture's own resolution is a hundred thousand million times coarser. Knowing when precision is irrelevant is as professional as knowing when it is not.
Do it now: a comparison that survives the machine#
Six cases, and the last three are the interesting ones. A tolerance that swallows a whole percent is not a tolerance, it is a bug being helpful. Pick a number you can defend, write down what it is a fraction of, and put the reason next to it in the code.
Control flow: the four shapes#
Every program ever written is made of four shapes, and the tree from the parser is just these nested.
Sequence. One thing after another, in order.
Branch. if, else. A decision, taken once, on a condition. The condition is where the bug usually is: > instead of >= is one fixture out of five hundred, or one frame out of ten thousand.
Loop. while or for. Repeat. Two questions, always: what changes each turn, and what makes it stop? A loop whose stop condition can never be reached is a hang, which is failure mode two from Class 1.
Call. Run a named piece of code elsewhere and come back with a result. This is the one that makes programs comprehensible to humans, because it lets you stop holding all of it in your head.
Functions, and reading a signature#
def fade(fixture, target, seconds=3.0):
"""Fade one fixture to a level over a time. Blocks until finished."""
Read that line before you read anything else. It tells you:
- Name. What it claims to do.
- Parameters. What it needs: a fixture, a target, and optionally a time that defaults to 3.0.
- Defaults. Which arguments you may leave out, and what happens if you do.
- Return. What comes back. Nothing here, which is itself information.
- The docstring. In this case one crucial word: blocks. This function does not return until the fade is finished. Call it for eight fixtures in a row and you have a 24 second cue during which nothing else in your program happens, including the emergency stop check.
Reading the signature and the first line of the docstring, before reading the body, is the single highest-value reading habit in this course. It is also how you review AI-generated code fast in Class 4: the signature tells you what it promises, and the body tells you whether it kept the promise.
Programming you do not type#
Most of the programming done in this industry is not typed. It is patched, wired and dragged, and a student who thinks that is not really programming will be wrong about something they will do every week for the rest of their career.
Go back to the parser from the top of this class. It turned your text into a tree: nodes with operations in them and edges carrying values between them. Execution walks that structure.
Now notice what a patcher is. TouchDesigner, Max/MSP, Pure Data, vvvv, Notch, Unreal Blueprints, Node-RED, Grasshopper: every one of them is that same graph, with the parsing step removed because you built the graph directly.
Text and boxes-and-wires are two notations for the same thing. The difference is the editing surface, not the semantics. There is no sense in which one is real programming and the other is not, and everything in this course applies unchanged to both.
Where you will actually meet it#
| Environment | What it is | Where it turns up |
|---|---|---|
| TouchDesigner | Node-based real-time video and control | Projection, interactive installations, media systems |
| Max/MSP, Pure Data | Audio and control patchers | Sound design, instrument building, interactive audio |
| Unreal Blueprints | Visual scripting over a game engine | Virtual production, LED volumes, previz |
| Notch, vvvv | Real-time graphics node graphs | Touring video, camera-tracked content |
| Node-RED | Wiring events between devices and services | Building systems, MQTT, small show integrations |
| Bitfocus Companion | Trigger and action rules, not a graph | Stream decks, integration glue, cue triggering |
| Grasshopper | Parametric geometry over Rhino | Scenic, rigging geometry, fabrication |
| Ladder logic (IEC 61131-3) | A graphical language for PLCs | Automation. The thing that moves scenery and lifts people |
| Console effect and macro editors | Whatever the manufacturer built | Lighting, media servers |
The last two rows deserve a pause. Ladder logic is a graphical programming language, it is what runs safety-rated automation, and it is a standard rather than a toy. If your career goes towards automation you will read more ladder than Python, and the four questions are the same questions.
Scratch, MakeCode and the block languages#
Blocks (Scratch, MakeCode, Blockly, micro:bit) are the teaching version of the same idea, and they are worth understanding rather than dismissing.
What they remove is syntax error as an obstacle. A block that will not fit cannot be dropped there, so a beginner spends their attention on sequence, branch, loop and call rather than on a missing semicolon. That is a real pedagogical win, and it is why they work: what is hard about programming was never the punctuation.
What they do not remove is anything in this course. A Scratch project has state, does something on each pass, assumes things, and behaves some way when it is wrong. If you are ever teaching this to someone with no background, or building a workshop for a young audience, blocks are the right first notation and the ideas transfer directly.
What each notation is actually better at#
Do it now: the same program, both ways#
Build anything. A fade, a chase, a condition. Then watch the panel on the right while you build.
That panel is not a translation and it is not a simplification. It is your program written out in three notations, generated from the same tree the runtime just walked. Blocks are not programming for beginners; they are programming with the spelling removed, which is exactly what a patcher window, a ladder diagram and a cue stack also are. Nobody using Max, TouchDesigner or a PLC thinks of themselves as writing code, and all three of them are.
The question worth arguing before you leave this section: what does the text version give you that the blocks do not? There are at least four answers and one of them is the reason every large system ends up in text.
Visual is better at: dataflow and signal, where the picture is genuinely the structure. Seeing state, because the value on a wire can be displayed on the wire. Real-time work where you are tuning by feel. And being read by somebody who does not write code, which in a production meeting is worth a great deal.
Do it now: the same program, both ways#
Text is better at: everything to do with keeping the work, which is Class 3. And it is worth being specific about why, because this is the trade nobody mentions when they demo a patch:
- Version control. Most patcher files are one binary or one enormous generated file.
git difftells you the file changed and nothing about what changed. Two people cannot merge two versions of a patch; one of them loses their afternoon. - Search. You cannot grep a patch for every place an address is used.
- Review. "Send me the diff" has no answer. Reviewing a patch means opening it and looking.
- Reuse. Copying a subpatch is copying, not referencing. Fix the bug in one and the other four still have it.
- Scale. Every patcher above about a few hundred nodes has the same disease, and it has a name in every one of those communities: spaghetti. The cure is discipline about subpatches, and it is the same discipline as functions.
None of that makes visual programming wrong. It makes it a tool with a known cost, and the professional move is to know the cost before the show rather than during the handover.
The four questions, in a patcher#
They do not change. They point at different things:
The four questions, in a patcher#
| Question | In text | In a patcher |
|---|---|---|
| What state does it hold? | Variables | What is stored inside the nodes, and what is only on a wire |
| What happens each pass? | One turn of the loop | One cook, one frame, one scan of the ladder |
| What does it assume? | Types and inputs | What each wire is carrying, and what happens if it is empty |
| What when it is wrong? | Exception, or worse | What a node outputs when its input is missing, and what the rest does with that |
The third row is the one that bites. In a lot of node environments an unconnected input silently becomes zero, or black, or the last value, and nothing anywhere reports it. That is failure mode four from Class 1, carry on regardless, built into the tool as the default.
Going deeperHello world, ten times#
Read this before the class rather than in it. It is a comparison rather than a demonstration, and you can read it faster than anybody can say it. Bring one question.
Going deeperHello world, ten times#
The traditional first program prints two words. It is a silly program and it is a genuinely useful comparison, because the ceremony around the one line tells you what the language cares about.
/* C, 1972 */
#include <stdio.h>
int main(void) {
printf("Hello, world\n");
return 0;
}Going deeperHello world, ten times#
// Arduino, which is C++ with the awkward parts hidden
void setup() {
Serial.begin(115200);
Serial.println("Hello, world");
}
void loop() {}# Python, 1991
print("Hello, world")Going deeperHello world, ten times#
// JavaScript, 1995
console.log("Hello, world");-- Lua, 1993
print("Hello, world")// Java, 1995
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world");
}
}Going deeperHello world, ten times#
// C#, 2000
Console.WriteLine("Hello, world");// Go, 2009
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}Going deeperHello world, ten times#
// Rust, 2010
fn main() {
println!("Hello, world");
}# The shell, older than most of the above
echo "Hello, world"And in a patcher there is no text at all: a message box containing Hello, world with a wire into a print object, which is two clicks and tells you something about that notation too.
What the ceremony is telling you#
C needs a header and a return code because it is a systems language from a world where the operating system needed to know whether your program succeeded, and where nothing is included unless you ask for it. That austerity is why it still runs the chip in your prop fifty years later.
Arduino needs a setup and a loop because the thing it runs on does one job forever and there is no operating system underneath to hand control back to. The shape of the program is the shape of the machine.
Python needs one line because it was designed on the principle that reading code is more common than writing it, and that the obvious way should be the short way.
Java needs a class, a public static void main and an array of arguments because it was built on the position that everything is an object, in an era of large teams and enterprise codebases where uniformity mattered more than brevity.
Go needs a package declaration and an import because it was made at Google for very large teams and very fast builds, and both come from being strict about structure and dependencies.
What the ceremony is telling you#
Rust's println! has an exclamation mark because it is a macro, expanded and checked at compile time. Almost everything unusual about Rust comes from moving checks earlier, into the compiler, where a mistake costs a build rather than a show.
Read the ceremony as a statement of values. A language that makes you write more before you can print two words is usually buying you something later: a guarantee, a build time, a team of forty people who can all read it.
Going deeperwhere each one came from, and what it owns now#
Going deeperwhere each one came from, and what it owns now#
Languages are not fashion. Each one exists because somebody had a problem that the languages of the day could not solve, and each one dominates a field because that field has that problem.
Going deeperwhere each one came from, and what it owns now#
| Language | Made | The problem it was made to solve | What it owns now, and why |
|---|---|---|---|
| Assembly | 1940s on | Writing machine instructions by hand was error-prone. Give them names | Almost nothing, by hand. You read it in a disassembly when firmware has gone badly wrong |
| Fortran | 1957 | Scientists needed to write formulas rather than instructions. The first widely used high-level language | Heavy numerical computing, still, because fifty years of trusted libraries do not get rewritten |
| C | 1972 | Writing an operating system in assembly was unmaintainable. Portable, close to the metal, no hidden cost | Firmware, kernels, embedded. Your Arduino, your console's guts, and almost everything below them |
| C++ | 1985 | C plus abstraction for large programs, without giving up the speed | Media servers, game engines, audio software, anything real-time where every millisecond is counted |
| Python | 1991 | Scripting that was actually pleasant to read. Explicitly designed against cleverness | Automation, data, scientific work, AI tooling, Raspberry Pi show control. It won because it is readable by people who are not programmers |
| Lua | 1993 | A tiny language to embed inside another application | Scripting inside consoles, media servers and games. You do not choose Lua; Lua is what is in the box |
| Java | 1995 | Write once, run anywhere, in an era of incompatible machines | Enterprise systems, Android, large back ends. Chosen for the ecosystem and the hiring pool more than the language |
| JavaScript | 1995 | Make a web page do something. Designed in ten days, and it shows | Every browser on earth, plus Node on the server. It owns the web by being the only option there |
| C# | 2000 | Microsoft's answer to Java, better designed and tied to Windows | Windows show tools, Unity, a lot of AV control software |
| Go | 2009 | Very large teams, very fast builds, easy concurrency | Network services and infrastructure. The tools your show network runs on are often written in it |
| Rust | 2010 | Memory safety without a garbage collector, so systems code stops having a whole class of bug | Slowly replacing C and C++ where safety matters. Increasingly in audio and media tooling |
| TypeScript | 2012 | JavaScript grew up and needed the static type checking from earlier in this class | Any browser or Node codebase past a few thousand lines, which is most of them now |
| Ladder logic | 1960s | Let electricians program controllers using the relay diagrams they already read | Industrial and entertainment automation. What moves scenery, and what is certified for it |
The pattern worth taking away#
Read that table by the middle column rather than the right one. Each language is an answer to a constraint of its moment: no memory, no speed, no compiler, no team, no safety.
A language dominates a field when its original constraint is that field's constraint. C owns embedded because it was made for machines with nothing to spare, and a microcontroller still has nothing to spare. Python owns automation because it was made for readability, and a script's real cost is somebody reading it in two years. Lua owns embedded scripting because it was made small enough to fit inside another program, and it still is.
So when somebody tells you a language is outdated, the useful question is not how old it is. It is whether the constraint it was built for still exists. For the machine in your prop, every one of C's constraints is still true, which is why a language from 1972 is the right answer in 2026 and will still be the right answer for a long time.
Which language, which notation, and why it is rarely your choice#
Numbers from this class#
| Thing | Value |
|---|---|
| Steps from source text to execution | Four: lex, parse, compile, execute |
Why 2 + 3 * 4 is 14 | Precedence is in the tree the parser builds |
| Bits in a double-precision float significand | 53 |
| Relative error of a double | About 1 part in 10¹⁶ |
0.1 + 0.2 in any IEEE 754 language | 0.30000000000000004 |
| Safe float comparison | abs(a - b) < tolerance |
| Range of an 8-bit unsigned integer | 0 to 255, which is one DMX slot |
| Range of a 16-bit unsigned integer | 0 to 65535, which is 16-bit pan or tilt |
| DMX universe size | 512 slots |
| What arrives from a file, network or serial port | Text, always, until you convert it |
| Simple values on assignment | Copied |
| Collections on assignment | Referenced, not copied |
| The four shapes of control flow | Sequence, branch, loop, call |
| The first thing to read in any function | Its signature, then one line of the docstring |
| What a patcher is | The parser's tree, edited directly, with the parsing step removed |
| Ladder logic | A graphical language in IEC 61131-3, and what runs safety-rated automation |
| What block languages remove | Syntax errors, not any of the four questions |
| Where a patch is weakest | Version control, search, review, reuse and scale |
| An unconnected input in most node environments | Silently zero, black or the last value, reported by nothing |
| C | 1972, for writing an operating system without assembly |
| Python | 1991, for scripting that is pleasant to read |
| JavaScript | 1995, designed in ten days, and it owns the browser by being the only option |
| Lua | 1993, small enough to embed inside another application |
| Rust | 2010, memory safety without a garbage collector |
| Ladder logic | 1960s, so electricians could program controllers from relay diagrams |
| Why a language dominates a field | Its original constraint is still that field's constraint |
| What the ceremony around hello world tells you | What the language decided to buy, and what it charged for it |
↑↓ move · ↵ open · esc close