# Lesson V To paint truly, first know the ink. ## Try ```prolog ?- word(add, In -- Out, Goal). In = [_A::int, _B::int|_S], Out = [_C::int|_S], Goal = (_C is _B + _A). ?- apply(add, [3, 2], Stack). Stack = [5]. ?- run([dup, add], [2], Stack). Stack = [4] ?- infer([dup, add], In, Out). In = [int|_S], Out = [int|_S]. ``` ## Learn Until now, every stack cell was opaque, so plain unification was enough. Arithmetic breaks that illusion: runtime must extract host payloads to compute, while inference must see only object-language types. The old `apply/3` and `infer/3` projections treated input and output patterns as ordinary stacks, so they could not express the boundary between host representation and language meaning. The replacement adds typed pattern erasure, binding, and building. > [!info]- Change > ```diff > - apply(Word, Stack0, Stack) :- > - word(Word, InPattern -- OutPattern, Goal), > - Stack0 = InPattern, > - call(Goal), > - Stack = OutPattern. > - > - infer([Word|Words], Stack0, Stack) :- > - word(Word, InPattern -- OutPattern, _Goal), > - Stack0 = InPattern, > - Stack1 = OutPattern, > - infer(Words, Stack1, Stack). > ``` Arithmetic is the first primitive family that has to inspect payloads. With the stack `[3, 2]`, `3` is the top item and `2` is below it, so `add` computes `2 + 3`. The same row says three things: * runtime should unpack two integer payloads and compute a third; * inference should require two `int` slots; * the rest of the stack `S` is preserved. ```prolog :- op(650, xfx, ::). word(add, [A::int, B::int|S] -- [C::int|S], C is B + A). word(sub, [A::int, B::int|S] -- [C::int|S], C is B - A). word(mul, [A::int, B::int|S] -- [C::int|S], C is B * A). ``` Runtime now needs more than raw unification. A typed pattern cell must check the object-language type of the input value, expose a Prolog payload for the primitive goal, and put the computed payload back onto the stack as an object-language value. This is where the interpreter starts decoupling host representation from language meaning. The row for `add` says it consumes two `int` payloads and produces one `int` payload. A Prolog integer and a language `int` are not the same concept. ```prolog apply(Word, Stack0, Stack) :- word(Word, InPattern -- OutPattern, Goal), bind(InPattern, Stack0), call(Goal), build(OutPattern, Stack). ``` Static inference projects directly from the same primitive row. The type-level reading is just `word/3` plus pattern erasure. ```prolog infer([], Stack, Stack). infer([Word|Words], Stack0, Stack) :- word(Word, InPattern -- OutPattern, _Goal), types(InPattern, Stack0), types(OutPattern, Stack1), infer(Words, Stack1, Stack). ``` Pattern projection erases runtime payload variables to obtain type stacks. A variable tail such as `S` must remain a variable tail; otherwise every primitive would accidentally require a closed stack. ```prolog types(Stack, Stack) :- var(Stack), !. types([], []). types([Item|Items], [Type|Types]) :- typed(Item, _Payload, Type), !, types(Items, Types). types([Type|Items], [Type|Types]) :- types(Items, Types). ``` `typed/3` is introduced here as a small recognizer for the new pattern form. The `nonvar/1` guard is part of that scaffolding. It means: only treat a pattern item as `Payload::Type` when the primitive row already wrote it that way. Open whole-cell variables such as the `A` in `dup` should remain whole stack cells, not be eagerly reinterpreted as payload views. ```prolog typed(Item, Payload, Type) :- nonvar(Item), Item = (Payload::Type). ``` Runtime binding is the input half of the runtime projection. A whole-item pattern such as `A` matches the whole stack cell. A typed payload pattern such as `A::int` asks whether the cell is an object-language `int`, then binds `A` to the host payload that the arithmetic goal should see. For now the value and payload are both the same Prolog integer, but that is an implementation coincidence. ```prolog bind(Pattern, Stack) :- var(Pattern), !, Pattern = Stack. bind([], []). bind([Item|Patterns], [Value|Values]) :- typed(Item, Payload, Type), !, unpack(Type, Value, Payload), bind(Patterns, Values). bind([Value|Patterns], [Value|Values]) :- bind(Patterns, Values). ``` Runtime building is the output half. The primitive goal computes a host payload such as `5`; the output pattern says that the result must return to the stack as an object-language `int` value. For now raw Prolog integers repesents object-language integers, so packing `int` is only validation plus identity. The important part is that `build/2` never treats the Prolog result as self-typed. It passes the payload through `pack/3`, where the interpreter decides what value the object language receives. For `add`, after `C is B + A` computes `C = 5`, the output pattern `C::int` asks `pack/3` to produce the stack cell. ```prolog build(Pattern, Stack) :- var(Pattern), !, Stack = Pattern. build([], []). build([Item|Patterns], [Value|Values]) :- typed(Item, Payload, Type), !, pack(Type, Payload, Value), build(Patterns, Values). build([Value|Patterns], [Value|Values]) :- build(Patterns, Values). ``` These two clauses are small because there is only one concrete type and its value representation is still bare. Their significance is structural: primitive goals work with host payloads, stacks contain object-language values, and only this pair translates between the two. ```prolog unpack(int, Value, Value) :- integer(Value). pack(int, Payload, Payload) :- integer(Payload). ``` [[04_lesson|Prev]] | [[06_lesson|Next]]