Progred is pre-alpha. Share on Zulip what clicked, and what confused you or broke.
A program draws this forest. Drag the slider and it grows.
Progred is an editor where a program is structure, not text. What you see are drawings of that structure, and you can edit through any of them. This forest started as a single dot. Below, you’ll rebuild it step by step, take it apart down to what’s stored, and end with a forest of your own.
A dot
This is the entire program, in Grap, a small programming language: fill a circle with a color. The small f64 after each number says what kind of number it is: a 64-bit floating-point number, what many languages call a double. The picture and the code are two drawings of the same thing, and each knows about the other.
- Not yet completed: Hold Ctrl and click the dot. Its code is selected.
- Not yet completed: Hold Ctrl and drag the 60 left and right. The dot slides as you drag, because 60 is a number you’re holding, not two characters.
Two dots
One dot isn’t a forest. Here are two, written out in full. The square brackets hold a list, and all draws everything in it.
- Not yet completed: Make both dots bigger.
That took two edits for one idea, and a forest would take fifty.
One recipe
So the dot becomes a recipe, a function. dot takes an x and fills in the rest, and each dot{x: …} below it is a call that supplies one. The → reads as “gives”. The circle inside changed too. The " makes it a template: it’s kept as written, except where ` marks a hole. Where the 60 was, `x is a hole each call fills with its own x.
- Not yet completed: Make both dots bigger again. It’s one edit now.
- Not yet completed: Hold Ctrl and click a dot. This time you land inside
dot, on thefillthat painted it. A click finds the deepest code on screen that drew what you clicked.
Dots become trees
The recipe got a trunk and green leaves, and every dot became a tree at once. But now the name is wrong.
- Not yet completed: In the recipe, click dot and rename it to tree.
Every call now says tree. Nothing was searched and replaced. So what is a call, if it doesn’t contain the name? Taking the forest apart, below, shows you.
Taller and shorter
A forest where every tree is the same height looks like a fence. So the recipe got a height. The trunks follow it, but the leaves still sit at 160 - 60, where a 60-tall tree’s leaves go. (y counts down from the top, and the ground is at 160.)
- Not yet completed: Hold Ctrl and drag the 60 in the leaves’
160 - 60until the middle tree’s leaves sit on its trunk.
Now the other two are wrong. No single number fits trees of different heights: each tree’s leaves need what its trunk uses, its own height. In the next step, they have it.
Growing together
The leaves now sit at 160 - height, like the trunks, so each tree computes its own, the way a spreadsheet formula does. To make the forest grow, every tree’s height has to follow one shared number. The parentheses around each 1 mark a cell, a value with its own identity. All three calls hold the same one, the way three formulas can use one spreadsheet cell.
- Not yet completed: Change any of the 1s to 0.5, or hold Ctrl and drag it. Every tree shrinks at once.
- Not yet completed: Plant a fourth tree by hand: click between the last two
treelines, typetree, press Enter, and give it an x between those two trees’, like 172, and a height.
Parameters have identities too: each call files its arguments under those same (x) and (height) cells, which is how the rename worked.
A forest
Now imagine planting fifty. So the forest counts them out instead. forest takes a tree count and a growth, and for each runs a small recipe once per tree. λ marks a recipe without a name, written right where it’s used; item is which tree it is, and sin gives each one its own height.
- Not yet completed: Set the tree count to 12.
- Not yet completed: Hold Ctrl and click any tree. You land in code you can read now.
The forest at the top of the page is the same idea, with a sky, a sun, and a slider driving its growth.
Take it apart
Here’s the forest from the top of the page again, and you can read its code now. Every mark on the screen, from the trees to the little f64 tags, is drawn by one of the editor’s libraries, plug-ins that each know how to draw one kind of thing. Each drawing is a projection of the same program, and you can edit through any of them. Switch off what the libraries draw, a layer at a time, then the names, and watch what’s underneath.
Try: before you peel, click the 7f64 after tree count. Then peel all the way down. It stays selected the whole time, while its drawing changes from 7f64 to eight bytes.
Names are labels
A cell’s name is only a fact stored about it: {name: "x"}. Calls don’t contain the word; they hold the cell itself. Below are the tree recipe, two calls to it, and the same calls again in the base projection, what’s left when no library draws. There, each call’s key is the cell’s identity, …10115: a random 128-bit id minted when the cell was created. Identities aren’t content hashes, so two cells with the same contents, or the same name, stay two different things.
- Not yet completed: In the recipe, click x and rename it. Every call’s label follows, and the bottom view doesn’t change at all.
That’s how renaming dot to tree worked: every call holds the recipe’s cell, not its name.
One value, many places
Parentheses mark a cell: a value with its own identity, stored once, that can sit in any number of places. The parentheses only show where it sits; they add nothing to what’s inside. Records, lists, blobs, and cells are the only things Progred builds in. The growth your trees shared was a cell, and so are functions, their parameters, and anything else you share.
- Not yet completed: Change either 7. Both change, because they’re one cell.
- Not yet completed: Make your own cell: click a comma and type
(, then 11, and press Enter. - Not yet completed: Reference it again: click another comma, then hold Ctrl and click a parenthesis around your new cell.
- Not yet completed: Change either 11. Its partner follows; the other pair doesn’t.
Parentheses select the reference; the inside edits the shared value.
The same bytes, read four ways
Text, numbers, and colors aren’t built in either. Each is a library that recognizes a blob filed under a key it owns. Below, one cell holds four bytes, and four views file a copy under different keys: one no library knows, the text library’s, the color library’s, and the f32 library’s.
- Not yet completed: Click the bytes at the top and type new hex digits. Every reading follows.
- Not yet completed: Give it only three bytes. The text still reads, but the color and the f32 decline and show you the record instead, because three bytes isn’t their convention.
What the little f64 means
The subscript on a number is the name of the key it sits under: f64 means a 64-bit float, one of several number libraries. The subscript on an operator names whose arithmetic it is. There’s an f64 +, an f32 +, a u64 +, and a Fidget + that builds shapes instead of numbers. When you write a call, you pick one, and the tag is that choice, remembered.
Try: compare 0.1 + 0.2 in f64 and f32. A float doesn’t stand for one exact number but for a small range of them, and the editor writes the shortest decimal inside that range. In f64 the sum lands on the float just above the one for 0.3, and the shortest decimal in its range takes seventeen digits. In f32 the sum lands on the same float as 0.3, so it prints as 0.3. Drag the shape to turn it, and change a radius.
Programs run before they’re finished
A call with a missing argument, or the wrong kind of value, doesn’t stop the program. It returns an absent: a value that says what’s missing and why, which the rest of the program can pass along or handle. or default is an ordinary function that recovers from one. The forest’s slider relies on it: before you touch the slider there’s no control state, and or default supplies 0.65f64.
- Not yet completed: Fill the empty
x. Its absent becomes a number.
The call given "three" returns an absent too, and its reason, left is not f64, comes from the x *f64 2f64 inside scale. Nothing ever refuses your edit while you’re in the middle of it.
Projections are programs
Every drawing you peeled off was a library’s projection: a function from a value to a drawing. You can write one in Grap and keep it in the same document as your data. This one draws planets.
- Not yet completed: Change moons to 5.
- Not yet completed: Change the planet’s color inside
planet drawing.
Here the projection draws a picture beside the data. A document can also declare a projection as part of a library: then it draws the document’s matching records in place, and the parts it shows stay editable, as in the Document Libraries example.
A forest of your own
Here’s the forest from the top of the page once more, with all of its code. It’s yours now. Some places to start:
- Change the colors of the sky, the sun, or the leaves.
- Plant more trees, or make them grow taller.
- Hold Ctrl and click anything in the picture to find the code that drew it, then change that.
Did it work for you?
Share on Zulip whether this made Progred click and where you got lost; the answers shape what gets built out.
Edits aren’t saved when you reload. Explore the full editor