Skip to content

Lists and dicts

list<T> is a Scratch list, with a type on the elements.

public scores: list<num> = [3, 1, 4, 1, 5];
public names: list<str> = ["Ember", "Splash"];
public empty: list<num> = [];

Scratch lists are 1-indexed, and so are Katnip’s.

temp first: num = scores[1];
scores[2] = scores[2] + 1;
scores[2] += 1; # compound assignment through an index
scores.add(7); # append
scores.clear(); # delete all
temp n: num = scores.length();
temp has: bool = scores.contains(4);
temp at: num = scores.indexOf(5); # 0 if absent
scores.show(); # show the list monitor
scores.hide();

Every method also has a namespace form — list.contains(scores, 4) — but it does not build today; codegen looks for a variable named list. Use the receiver form.

for (s, scores) {
total += s;
}

dict<K, V> has no Scratch equivalent. Katnip backs each dict with two parallel Scratch lists — for stock, they are stock_keys and stock_vals — and keeps their indices in step.

public stock: dict<str, num> = {"apple": 2, "banana": 5};
temp apples: num = stock["apple"];
stock["cherry"] = 7; # key missing → appended
stock["apple"] = 3; # key present → replaced in place
stock["apple"] += 1;

A read resolves the key column to an index, then reads the value column at that index.

for ((name, count), stock) {
report(name, count);
}

The (name, count) tuple pattern walks both columns together.

Only at the top level or in a sprite body. A list or dict declared inside a proc or a handler is not lowered:

proc bad() -> void {
temp working: list<num> = []; # NOT lowered
}
public working: list<num> = []; # do this instead

Scratch has no local lists either, so this is less of a restriction than it sounds — but it does mean shared scratch space is genuinely shared, including across clones and recursive calls.

If every element is a literal, the contents are baked into the project file and are present the moment the project loads:

public scores: list<num> = [3, 1, 4, 1, 5];

If any element needs a block to compute, the whole list is instead rebuilt by a green-flag script:

public roster: list<num> = [1, double(4), 9];

That distinction matters when another green-flag script reads the list — Scratch does not order concurrent scripts for you. If you depend on it, broadcast after the rebuild rather than racing it.

temp c: str = greeting[1]; # 1-based, operator_letter_of
temp n: num = len(greeting);
temp has: bool = greeting.contains("at");
for (letter, greeting) { ... }

There is no slicing. s[1:5:2] parses and gets a naive type, but has no lowering.

temp paired: (list<str>, list<num>) = zip(names, powers);
temp tagged: (list<num>, list<str>) = enumerate(names);

The types infer correctly — T binds from the arguments. But zip and enumerate are katnip_* builtins with no codegen, so they type-check and then fail the build. Walk by index instead:

for (i, names.length()) {
report(names[i], powers[i]);
}