Modules · Core · Console
Write text to the console. Console gives you Console.Line , which appends a line to the console output channel — a reactive sink the console driver drains and prints on each cycle. Because the call runs inside the reactive program, a line written from a value that later changes is simply written again on the next cycle.
Sets the background colour for a block.
Returns nothing — every piece of text or line written in the content is drawn on the given background colour.
Sets the text colour for a block.
Returns nothing — every piece of text or line written in the content is drawn in the given foreground colour.
Writes a line of text to the console output.
Returns nothing — the line is appended to the console output channel.
Prefixes every line written in a block.
Returns nothing — the content is written to the console output channel with the prefix applied to the start of each line.
Writes text without ending the line.
Returns nothing — the text is appended to the current line of the console output channel, with no trailing line break. When it is the first thing written on a new line, the active prefix is written before it.
The open question above is now settled. At the call site each hole is named in full — the function's name, then the hole's own — so the label cannot be mistaken for anything else. A bare sample would be tempting, but something else already in scope might be called sample too (a piece of state , say), and the two would be ambiguous; the fully-qualified Scene.Field.sample can clash with nothing. (Its lowercase final segment also keeps it distinct from a call , which is capitalised.) So Console.Prefix 's two holes are written in full:
A single-hole call keeps a shorthand: with only one block to fill there is nothing to disambiguate, so the label is dropped and the block goes straight into the call's indented content, as Console.Foreground shows. This is worth keeping because the single-hole call is the common case — most of the user interface is containers with a single content block — and forcing a label there would roughly double the line count for no gain. Names are needed only once a call offers more than one block.
A hole's block can take arguments, and it binds them the way a function does — with INPUT lines — returning its value as the block's last expression. Picture a Scene.Material that wraps some geometry in a material: its material hole is handed a point in space and returns the colour there, and its object hole takes nothing and just places the geometry.
At the call site each block is still opened by its hole name. material binds the coordinate it is given and returns a colour; object takes no arguments:
A hole's types can depend on a type parameter of the function , chosen by the caller. A procedural Scene.Field is a fair motivating case: it is generic over the value it samples, SampleType , declared in parentheses on the function. That one parameter then appears inside the holes — as sample 's return, and as the type of shade 's INPUT :
The caller supplies that type once, in parentheses on the call — exactly as any other generic call already does — and it flows into both blocks. Here it is a scalar field: Perlin noise sampled per point, shaded to grey.
A hole need not be filled with a fresh block. When an existing function already has the shape a hole wants, it can be named directly — but the exact spelling is still open. Three forms are on the table, each shown filling both of Scene.Field 's holes ( Maths.Noise.Perlin maps a coordinate to a value, Colour.RGB.Grey a value to a colour).
Label, then reference. The hole's qualified label and then the function, on one line. Explicit, but a label and a function sitting side by side read a little like two calls run together — which is what makes it feel slightly off:
Named argument. The hole's name, an = , then the function. A bare word followed by = is a shape the language uses nowhere else, so the name needs no qualifying to stay unambiguous — a state also called sample can't be confused with it — and it reads like the assignment it is:
The same name = carries a whole block just as easily. When the value is not a reference but a fresh function, it follows the = , indented beneath — the return type is not repeated, since the hole already fixes it, and no FUNCTION keyword is needed, because a named argument whose value is a block can only be a hole:
So one form scales from a reference to a full block. It also generalises past holes: the same name = value could name ordinary value arguments too, whenever a call reads more clearly with them spelled out rather than left positional.
Positional. Keep the holes positional, like every other argument, and either drop in a reference or an inline anonymous FUNCTION . No new syntax at all, but the reader has to count positions to see which hole is which: