# 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 ```prolog % Arithmetic is the first primitive family that has to inspect % payloads. With 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. :- 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. It does not say that % "Prolog integer" and "language int" are the same concept. % `unpack/3` and `pack/3` keep that fact local: % % unpack(int, int(5), 5) % pack(int, 5, int(5)) % % Only the boundary changes. 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. % There is no extra static table: the type-level reading is just % `word/3` plus pattern erasure. 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. 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 says: 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. 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. % % In this stage the value and payload are both the same Prolog % integer, but that is an implementation coincidence. `unpack/3` % names the act of crossing from the language's value world into % Prolog's computation world. For `add`, the first two bindings are: % % A::int with stack value 3 gives payload A = 3 % B::int with stack value 2 gives payload B = 2 % % The arithmetic goal receives `A` and `B`, not the original pattern % cells. 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 it uses raw Prolog integers as 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. 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, not computational: primitive goals work with host % payloads, stacks contain object-language values, and only this pair % translates between the two. unpack(int, Value, Value) :- integer(Value). pack(int, Payload, Payload) :- integer(Payload). ```