1:- module(hash_consing,
2 [ intern/2, 3 externalized/2, 4 internalized/3, 5 rewritten/1 6 ]).
229:- use_module(library(error), [must_be/2]). 230:- use_module(library(lists), [reverse/2, append/3]). 231
232:- initialization(stores_created).
237stores_created :-
238 trie_new(Store),
239 nb_setval(hash_consing_store, Store),
240 trie_new(Spellings),
241 nb_setval(hash_consing_spellings, Spellings).
242
247intern(Term, Id) :-
248 nonvar(Id),
249 !,
250 id_parts(Id, _, _, Handle),
251 ( nonvar(Handle)
252 -> trie_term(Handle, Stored),
253 Term = Stored
254 ; upserted(Term, Id)
255 ).
256intern(Term, Id) :-
257 upserted(Term, Id).
261upserted(Term, Id) :-
262 ( ground(Term)
263 -> true
264 ; throw(error(instantiation_error, context(hash_consing:intern/2, Term-Id)))
265 ),
266 nb_getval(hash_consing_store, Trie),
267 ( trie_lookup(Trie, Term, Handle)
268 -> true
269 ; trie_insert(Trie, Term, pending, Handle),
270 trie_update(Trie, Term, Handle)
271 ),
272 functor(Term, Name, Arity),
273 configuration_of(Name, Arity, Configuration),
274 id_of(Configuration, Term, Name, Arity, Handle, Id).
275
276id_of(root_only, _, Name, Arity, Handle, Id) :-
277 id_name(Name, Arity, IdName),
278 compound_name_arguments(Id, IdName, [Handle]).
279id_of(shaped(Records), Term, Name, Arity, Handle, Id) :-
280 id_name(Name, Arity, IdName),
281 ( term_shape(Term, Records, [], Shape)
282 -> true
283 ; throw(error(instantiation_error, context(hash_consing:intern/2, Term)))
284 ),
285 compound_name_arguments(Id, IdName, [Shape, Handle]).
291id_parts(Value, IdName, Shape, Handle) :-
292 compound(Value),
293 compound_name_arity(Value, IdName, Arity),
294 sub_atom(IdName, 0, _, _, '__hash_consed_'),
295 id_arguments(Arity, Value, Shape, Handle).
296
297id_arguments(1, Value, none, Handle) :-
298 arg(1, Value, Handle).
299id_arguments(2, Value, Shape, Handle) :-
300 arg(1, Value, Shape),
301 arg(2, Value, Handle).
306id_pattern(Name, Arity, Id, Handle) :-
307 id_name(Name, Arity, IdName),
308 configuration_of(Name, Arity, Configuration),
309 ( Configuration == root_only
310 -> compound_name_arguments(Id, IdName, [Handle])
311 ; compound_name_arguments(Id, IdName, [_, Handle])
312 ).
317:- dynamic known_id_name/3. 318:- dynamic id_constructor/3. 319
320id_name(Name, Arity, IdName) :-
321 ( known_id_name(Name, Arity, Known)
322 -> IdName = Known
323 ; format(atom(Made), '__hash_consed_~w/~d', [Name, Arity]),
324 assertz(known_id_name(Name, Arity, Made)),
325 assertz(id_constructor(Made, Name, Arity)),
326 IdName = Made
327 ).
328
330
331:- dynamic constructor_configuration/3.
339configuration_of(Name, Arity, Configuration) :-
340 ( constructor_configuration(Name, Arity, Known)
341 -> Configuration = Known
342 ; assertz(constructor_configuration(Name, Arity, root_only)),
343 Configuration = root_only
344 ).
349configuration_registered(Name, Arity, Configuration) :-
350 ( constructor_configuration(Name, Arity, Known)
351 -> ( Known == Configuration
352 -> true
353 ; throw(error(permission_error(reconfigure, interned_constructor, Name/Arity),
354 context(hash_consing:rewritten/1,
355 configured(Known)-declared(Configuration))))
356 )
357 ; assertz(constructor_configuration(Name, Arity, Configuration))
358 ).
364specification(Template, _, _, _) :-
365 var(Template),
366 !,
367 throw(error(instantiation_error, context(hash_consing:rewritten/1, _))).
368specification(Template, Name, Arity, Configuration) :-
369 callable(Template),
370 Template \== (*),
371 Template \= (_ ; _),
372 !,
373 compound_name_arguments_or_atom(Template, Name, Arguments),
374 length(Arguments, Arity),
375 arguments_records(Arguments, Records),
376 ( all_ignored(Records)
377 -> Configuration = root_only
378 ; Configuration = shaped(Records)
379 ).
380specification(Template, _, _, _) :-
381 throw(error(type_error(constructor_template, Template), _)).
382
383compound_name_arguments_or_atom(Term, Name, Arguments) :-
384 ( atom(Term)
385 -> Name = Term,
386 Arguments = []
387 ; compound_name_arguments(Term, Name, Arguments)
388 ).
389
390arguments_records([], []).
391arguments_records([Argument | Arguments], [Record | Records]) :-
392 argument_record(Argument, Record),
393 arguments_records(Arguments, Records).
394
395argument_record(Argument, ignored) :-
396 var(Argument),
397 !.
398argument_record(*, root([])) :-
399 !.
400argument_record(Alternatives, root(Templates)) :-
401 alternatives_list(Alternatives, List),
402 templates_below(List, Templates).
403
404alternatives_list(Alternatives, List) :-
405 ( nonvar(Alternatives),
406 Alternatives = (Left ; Right)
407 -> alternatives_list(Left, LeftList),
408 alternatives_list(Right, RightList),
409 append(LeftList, RightList, List)
410 ; List = [Alternatives]
411 ).
412
415templates_below([], []).
416templates_below([Template | Templates], Belows) :-
417 specification(Template, Name, Arity, Configuration),
418 ( Configuration = shaped(Records)
419 -> Belows = [below(Name/Arity, Records) | Rest]
420 ; Belows = Rest
421 ),
422 templates_below(Templates, Rest),
423 ( memberchk(below(Name/Arity, _), Rest)
424 -> throw(error(domain_error(one_template_per_constructor, Template), _))
425 ; true
426 ).
427
428all_ignored([]).
429all_ignored([ignored | Records]) :-
430 all_ignored(Records).
431
433
446term_shape(Term, Records, Occurrences, Atom) :-
447 compound_name_arguments_or_atom(Term, _, Arguments),
448 arguments_descriptions(Records, Arguments, Occurrences, Structure),
449 nb_getval(hash_consing_spellings, Spellings),
450 ( trie_lookup(Spellings, Structure, Known)
451 -> Atom = Known
452 ; structure_atom(Structure, Atom),
453 trie_insert(Spellings, Structure, Atom)
454 ).
455
456arguments_descriptions([], [], _, []).
457arguments_descriptions([ignored | Records], [_ | Arguments], Occurrences, Descriptions) :-
458 !,
459 arguments_descriptions(Records, Arguments, Occurrences, Descriptions).
460arguments_descriptions([root(Templates) | Records], [Argument | Arguments], Occurrences,
461 [Description | Descriptions]) :-
462 argument_description(Argument, Templates, Occurrences, Description),
463 arguments_descriptions(Records, Arguments, Occurrences, Descriptions).
464
465argument_description(Argument, _, _, _) :-
466 var(Argument),
467 !,
468 fail.
469argument_description(Argument, Templates, Occurrences, constructor(Name/Arity, Inner)) :-
470 id_parts(Argument, IdName, _, _),
471 !,
472 id_constructor(IdName, Name, Arity),
473 ( memberchk(below(Name/Arity, Records), Templates)
474 -> id_term(Argument, Occurrences, Below),
475 compound_name_arguments_or_atom(Below, _, BelowArguments),
476 arguments_descriptions(Records, BelowArguments, Occurrences, Inner)
477 ; Inner = none
478 ).
479argument_description(_, _, _, other).
485id_term(Id, Occurrences, Term) :-
486 id_parts(Id, _, _, Handle),
487 ( nonvar(Handle)
488 -> trie_term(Handle, Term)
489 ; occurrence_term(Occurrences, Id, Term)
490 ).
491
492occurrence_term([occurrence(Candidate, CandidateId, _) | Occurrences], Id, Term) :-
493 ( CandidateId == Id
494 -> Term = Candidate
495 ; occurrence_term(Occurrences, Id, Term)
496 ).
501structure_atom(Structure, Atom) :-
502 descriptions_texts(Structure, Texts),
503 atomic_list_concat(Texts, ',', Atom).
504
505descriptions_texts([], []).
506descriptions_texts([Description | Descriptions], [Text | Texts]) :-
507 description_text(Description, Text),
508 descriptions_texts(Descriptions, Texts).
509
510description_text(other, '-').
511description_text(constructor(Name/Arity, none), Text) :-
512 !,
513 format(atom(Text), '~w/~d', [Name, Arity]).
514description_text(constructor(Name/Arity, Inner), Text) :-
515 structure_atom(Inner, InnerText),
516 format(atom(Text), '~w/~d(~w)', [Name, Arity, InnerText]).
517
523externalized(Term, External) :-
524 var(Term),
525 !,
526 External = Term.
527externalized(Term, External) :-
528 id_parts(Term, _, _, Handle),
529 !,
530 ( nonvar(Handle)
531 -> trie_term(Handle, Stored),
532 externalized(Stored, External)
533 ; throw(error(instantiation_error, context(hash_consing:externalized/2, Term)))
534 ).
535externalized(Term, External) :-
536 compound(Term),
537 !,
538 compound_name_arguments(Term, Name, Arguments),
539 arguments_externalized(Arguments, ExternalArguments),
540 compound_name_arguments(External, Name, ExternalArguments).
541externalized(Term, Term).
542
543arguments_externalized([], []).
544arguments_externalized([Argument | Arguments], [External | Externals]) :-
545 externalized(Argument, External),
546 arguments_externalized(Arguments, Externals).
553internalized(Constructors, External, TermWithIds) :-
554 must_be(list, Constructors),
555 specifications_indicators(Constructors, Indicators),
556 term_internalized(External, Indicators, TermWithIds).
557
558specifications_indicators([], []).
559specifications_indicators([Element | Elements], [Name/Arity | Indicators]) :-
560 specification(Element, Name, Arity, Configuration),
561 configuration_registered(Name, Arity, Configuration),
562 specifications_indicators(Elements, Indicators).
563
564term_internalized(Term, _, Internal) :-
565 var(Term),
566 !,
567 Internal = Term.
568term_internalized(Term, _, Internal) :-
569 id_parts(Term, _, _, _),
570 !,
571 Internal = Term.
572term_internalized(Term, Indicators, Internal) :-
573 compound(Term),
574 !,
575 compound_name_arguments(Term, Name, Arguments),
576 arguments_internalized(Arguments, Indicators, InternalArguments),
577 compound_name_arguments(Rebuilt, Name, InternalArguments),
578 compound_name_arity(Term, Name, Arity),
579 instance_internalized(Name/Arity, Indicators, Rebuilt, Internal).
580term_internalized(Term, Indicators, Internal) :-
581 atom(Term),
582 !,
583 instance_internalized(Term/0, Indicators, Term, Internal).
584term_internalized(Term, _, Term).
585
586arguments_internalized([], _, []).
587arguments_internalized([Argument | Arguments], Indicators, [Internal | Internals]) :-
588 term_internalized(Argument, Indicators, Internal),
589 arguments_internalized(Arguments, Indicators, Internals).
590
591instance_internalized(Indicator, Indicators, Term, Internal) :-
592 ( memberchk(Indicator, Indicators)
593 -> intern(Term, Internal)
594 ; Internal = Term
595 ).
596
602:- dynamic registered_constructor/3. 603
604rewritten(Constructors) :-
605 must_be(list, Constructors),
606 ( prolog_load_context(source, File)
607 -> true
608 ; throw(error(context_error(nodirective, hash_consing:rewritten/1), _))
609 ),
610 constructors_registered(Constructors, File).
611
612constructors_registered([], _).
613constructors_registered([Element | Elements], File) :-
614 specification(Element, Name, Arity, Configuration),
615 configuration_registered(Name, Arity, Configuration),
616 id_name(Name, Arity, _),
617 ( registered_constructor(File, Name, Arity)
618 -> true
619 ; assertz(registered_constructor(File, Name, Arity))
620 ),
621 constructors_registered(Elements, File).
622
628source_rewritten(Source, _) :-
629 var(Source),
630 !,
631 fail.
632source_rewritten(end_of_file, _) :-
633 !,
634 prolog_load_context(source, File),
635 prolog_load_context(file, File),
636 retractall(registered_constructor(File, _, _)),
637 fail.
638source_rewritten((:- _), _) :-
639 !,
640 fail.
641source_rewritten((?- _), _) :-
642 !,
643 fail.
644source_rewritten(Source, Rewritten) :-
645 prolog_load_context(source, File),
646 registered_constructor(File, _, _),
647 !,
648 clause_rewritten(Source, File, Candidate),
649 Candidate \=@= Source,
650 Rewritten = Candidate.
651
652clause_rewritten((Head --> Body), File, (Head --> Body)) :-
653 !,
654 listed_absent((Head --> Body), File, dcg_rule).
655clause_rewritten((Head :- Body), File, Rewritten) :-
656 !,
657 rule_rewritten(Head, Body, File, Rewritten).
658clause_rewritten(Head, File, Rewritten) :-
659 rule_rewritten(Head, true, File, Rewritten).
664rule_rewritten(Qualifier:Head0, Body0, File, (Qualifier:Head :- Body)) :-
665 !,
666 plain_rule_rewritten(Head0, Body0, File, Head, Body).
667rule_rewritten(Head0, Body0, File, (Head :- Body)) :-
668 plain_rule_rewritten(Head0, Body0, File, Head, Body).
669
670plain_rule_rewritten(Head0, Body0, File, Head, Body) :-
671 arguments_of_abstracted(Head0, File, Head, Occurrences),
672 body_rewritten(Body0, File, RewrittenBody),
673 head_scheduled(Occurrences, RewrittenBody, Body).
680arguments_of_abstracted(Callable0, File, Callable, Occurrences) :-
681 compound(Callable0),
682 !,
683 compound_name_arguments(Callable0, Name, Arguments0),
684 arguments_abstracted(Arguments0, File, Arguments, [], Reversed),
685 compound_name_arguments(Callable, Name, Arguments),
686 reverse(Reversed, BottomUp),
687 occurrences_baked(BottomUp, Occurrences),
688 shapes_determined(Occurrences).
689arguments_of_abstracted(Callable, _, Callable, []).
690
691arguments_abstracted([], _, [], Reversed, Reversed).
692arguments_abstracted([Argument0 | Arguments0], File, [Argument | Arguments], Reversed0, Reversed) :-
693 abstracted(Argument0, File, Argument, Reversed0, Reversed1),
694 arguments_abstracted(Arguments0, File, Arguments, Reversed1, Reversed).
700abstracted(Term, _, Term, Reversed, Reversed) :-
701 var(Term),
702 !.
703abstracted(Term0, File, Term, Reversed0, Reversed) :-
704 compound(Term0),
705 !,
706 compound_name_arguments(Term0, Name, Arguments0),
707 arguments_abstracted(Arguments0, File, Arguments, Reversed0, Reversed1),
708 compound_name_arguments(Layer, Name, Arguments),
709 compound_name_arity(Term0, Name, Arity),
710 occurrence_abstracted(Name, Arity, Layer, File, Term, Reversed1, Reversed).
711abstracted(Term0, File, Term, Reversed0, Reversed) :-
712 atom(Term0),
713 !,
714 occurrence_abstracted(Term0, 0, Term0, File, Term, Reversed0, Reversed).
715abstracted(Term, _, Term, Reversed, Reversed).
716
717occurrence_abstracted(Name, Arity, Layer, File, Term, Reversed0, Reversed) :-
718 ( registered_constructor(File, Name, Arity)
719 -> id_pattern(Name, Arity, Term, Handle),
720 Reversed = [occurrence(Layer, Term, Handle) | Reversed0]
721 ; Term = Layer,
722 Reversed = Reversed0
723 ).
730occurrences_baked([], []).
731occurrences_baked([occurrence(Term, Id, Handle) | Occurrences], Remaining) :-
732 ( ground(Term)
733 -> intern(Term, Id),
734 occurrences_baked(Occurrences, Remaining)
735 ; Remaining = [occurrence(Term, Id, Handle) | Rest],
736 occurrences_baked(Occurrences, Rest)
737 ).
747shapes_determined(Occurrences) :-
748 shapes_determined(Occurrences, Occurrences).
749
750shapes_determined([], _).
751shapes_determined([occurrence(Term, Id, _) | Rest], Occurrences) :-
752 ( id_parts(Id, _, Shape, _),
753 var(Shape),
754 compound_name_arguments_or_atom(Term, Name, Arguments),
755 length(Arguments, Arity),
756 constructor_configuration(Name, Arity, shaped(Records)),
757 term_shape(Term, Records, Occurrences, Atom)
758 -> Shape = Atom
759 ; true
760 ),
761 shapes_determined(Rest, Occurrences).
766head_scheduled([], Body, Body) :-
767 !.
768head_scheduled(BottomUp, Body0, (Condition -> Fast ; General)) :-
769 reverse(BottomUp, TopDown),
770 top_level_occurrences(BottomUp, BottomUp, TopLevel),
771 handles_bound(TopLevel, Condition),
772 occurrences_goal(TopDown, intern, Lookups),
773 occurrences_goal(TopDown, intern_if_bound, Settled),
774 occurrences_goal(BottomUp, intern_if_unbound_and_ground, Inserted),
775 occurrences_goal(BottomUp, intern_if_unbound, Finished),
776 conjoined([Lookups, Body0], Fast),
777 conjoined([Settled, Inserted, Body0, Finished], General).
781conjoined(Goals, Conjunction) :-
782 goals_kept(Goals, Kept),
783 kept_conjoined(Kept, Conjunction).
784
785goals_kept([], []).
786goals_kept([Goal | Goals], Kept) :-
787 ( Goal == true
788 -> Kept = Rest
789 ; Kept = [Goal | Rest]
790 ),
791 goals_kept(Goals, Rest).
792
793kept_conjoined([], true).
794kept_conjoined([Goal], Goal) :-
795 !.
796kept_conjoined([Goal | Goals], (Goal, Conjunction)) :-
797 kept_conjoined(Goals, Conjunction).
798
799top_level_occurrences([], _, []).
800top_level_occurrences([Occurrence | Occurrences], All, TopLevel) :-
801 Occurrence = occurrence(_, _, Handle),
802 ( nested_in_another(Handle, All)
803 -> TopLevel = Rest
804 ; TopLevel = [Occurrence | Rest]
805 ),
806 top_level_occurrences(Occurrences, All, Rest).
807
808nested_in_another(Handle, [occurrence(Term, _, _) | Occurrences]) :-
809 ( term_variables(Term, Variables),
810 variable_member(Handle, Variables)
811 -> true
812 ; nested_in_another(Handle, Occurrences)
813 ).
814
815variable_member(Variable, [Candidate | Candidates]) :-
816 ( Variable == Candidate
817 -> true
818 ; variable_member(Variable, Candidates)
819 ).
820
821handles_bound([occurrence(_, _, Handle)], nonvar(Handle)) :-
822 !.
823handles_bound([occurrence(_, _, Handle) | Occurrences], (nonvar(Handle), Condition)) :-
824 handles_bound(Occurrences, Condition).
829occurrences_goal([], _, true).
830occurrences_goal([Occurrence], Step, Goal) :-
831 !,
832 occurrence_goal(Step, Occurrence, Goal).
833occurrences_goal([Occurrence | Occurrences], Step, (Goal, Goals)) :-
834 occurrence_goal(Step, Occurrence, Goal),
835 occurrences_goal(Occurrences, Step, Goals).
836
837occurrence_goal(intern, occurrence(Term, Id, _),
838 hash_consing:intern(Term, Id)).
839occurrence_goal(intern_if_bound, occurrence(Term, Id, Handle),
840 (nonvar(Handle) -> hash_consing:intern(Term, Id) ; true)).
841occurrence_goal(intern_if_unbound, occurrence(Term, Id, Handle),
842 (var(Handle) -> hash_consing:intern(Term, Id) ; true)).
843occurrence_goal(intern_if_unbound_and_ground, occurrence(Term, Id, Handle),
844 ((var(Handle), ground(Term)) -> hash_consing:intern(Term, Id) ; true)).
845occurrence_goal(intern_if_ground, occurrence(Term, Id, _),
846 (ground(Term) -> hash_consing:intern(Term, Id) ; true)).
851body_rewritten(Goal, _, Goal) :-
852 var(Goal),
853 !.
854body_rewritten((Left0, Right0), File, (Left, Right)) :-
855 !,
856 body_rewritten(Left0, File, Left),
857 body_rewritten(Right0, File, Right).
858body_rewritten((Left0 ; Right0), File, (Left ; Right)) :-
859 !,
860 body_rewritten(Left0, File, Left),
861 body_rewritten(Right0, File, Right).
862body_rewritten((Condition0 -> Then0), File, (Condition -> Then)) :-
863 !,
864 body_rewritten(Condition0, File, Condition),
865 body_rewritten(Then0, File, Then).
866body_rewritten((Condition0 *-> Then0), File, (Condition *-> Then)) :-
867 !,
868 body_rewritten(Condition0, File, Condition),
869 body_rewritten(Then0, File, Then).
870body_rewritten(\+ Goal0, File, \+ Goal) :-
871 !,
872 body_rewritten(Goal0, File, Goal).
873body_rewritten(call(Goal0), File, call(Goal)) :-
874 !,
875 body_rewritten(Goal0, File, Goal).
876body_rewritten(forall(Condition0, Action0), File, forall(Condition, Action)) :-
877 !,
878 body_rewritten(Condition0, File, Condition),
879 body_rewritten(Action0, File, Action).
880body_rewritten(findall(Template, Goal0, Result), File, findall(Template, Goal, Result)) :-
881 !,
882 listed_absent(Template-Result, File, findall_template_or_result),
883 body_rewritten(Goal0, File, Goal).
884body_rewritten(catch(Goal0, Catcher, Recovery0), File, catch(Goal, Catcher, Recovery)) :-
885 !,
886 listed_absent(Catcher, File, catch_catcher),
887 body_rewritten(Goal0, File, Goal),
888 body_rewritten(Recovery0, File, Recovery).
889body_rewritten(Qualifier:Goal0, File, Qualifier:Goal) :-
890 !,
891 body_rewritten(Goal0, File, Goal).
892body_rewritten(Goal0, File, Goal) :-
893 goal_rewritten(Goal0, File, Goal).
898goal_rewritten(Goal0, File, Goal) :-
899 arguments_of_abstracted(Goal0, File, Called, BottomUp),
900 goal_scheduled(BottomUp, Called, Goal).
901
902goal_scheduled([], Called, Called) :-
903 !.
904goal_scheduled(BottomUp, Called, (ground(Variables) -> Fast ; General)) :-
905 reverse(BottomUp, TopDown),
906 terms_variables(BottomUp, Variables),
907 occurrences_goal(BottomUp, intern, Inserts),
908 occurrences_goal(BottomUp, intern_if_ground, Settled),
909 occurrences_goal(TopDown, intern, Finished),
910 conjoined([Inserts, Called], Fast),
911 conjoined([Settled, Called, Finished], General).
916terms_variables(Occurrences, Variables) :-
917 occurrences_terms(Occurrences, Terms),
918 term_variables(Terms, All),
919 occurrences_handles(Occurrences, Handles),
920 variables_without(All, Handles, Variables).
921
922occurrences_terms([], []).
923occurrences_terms([occurrence(Term, _, _) | Occurrences], [Term | Terms]) :-
924 occurrences_terms(Occurrences, Terms).
925
926occurrences_handles([], []).
927occurrences_handles([occurrence(_, _, Handle) | Occurrences], [Handle | Handles]) :-
928 occurrences_handles(Occurrences, Handles).
929
930variables_without([], _, []).
931variables_without([Variable | Variables], Excluded, Kept) :-
932 ( variable_member(Variable, Excluded)
933 -> Kept = Rest
934 ; Kept = [Variable | Rest]
935 ),
936 variables_without(Variables, Excluded, Rest).
941listed_absent(Term, File, Place) :-
942 abstracted(Term, File, _, [], Occurrences),
943 ( Occurrences == []
944 -> true
945 ; throw(error(domain_error(term_without_rewritten_constructor, Term),
946 context(hash_consing:rewritten/1, Place)))
947 ).
948
951
952:- multifile user:term_expansion/2. 953:- dynamic user:term_expansion/2. 954
955user:term_expansion(Source, Rewritten) :-
956 hash_consing:source_rewritten(Source, Rewritten)
hash_consing: hash-consed terms whose Ids record a configurable shape, and a term rewrite that makes a program use them
A GENERIC LIBRARY: it knows no constructor of any calculus and keeps no configuration but the templates files declare.
HOW TO USE IT. A file is written as the program that does not intern, loads this library and names the constructors to intern, each by a template:
:- module(steps, [step/2, built/2]). :- use_module(library(hash_consing), []). :- hash_consing:rewritten([apply(*, _), lambda(_), variable(_)]). step(apply(lambda(Body), Argument), beta(Body, Argument)). built(Function, Applied) :- Applied = apply(Function, variable(0)).Files that hand such terms to each other declare the same templates, most simply by including one file that holds the directive. Code that did not go through the rewrite crosses the boundary explicitly:
?- hash_consing:internalized([apply(*, _), lambda(_), variable(_)], apply(lambda(variable(0)), variable(1)), Id), steps:step(Id, Result), hash_consing:externalized(Result, External). Id = '__hash_consed_apply/2'('lambda/1', <handle>), External = beta(variable(0), variable(1)).With the list
[]the file loads unchanged, which is the program without interning. The testtest/hash_consing_rewrite.shprints what the rewrite makes of its fixtures, `test/hash_consing_fixtures/`, and is the place to read exact rewritten clauses.`intern(?Term, ?Id)` IS ONE RELATION WITH TWO DIRECTIONS (user, 2026-09-17: a binary predicate that upserts forwards and looks up backwards). Term is a ground term whose rewritten subterms are Ids already. Called with Id bound it looks the term up (and with Term bound as well it is a check that inserts nothing); called with Id unbound, or bound to an Id pattern whose Handle is unbound, it inserts Term unless it is there and answers its Id; a value that is no Id matches nothing and the call fails, as a pattern that does not match fails without interning (user ruling, 2026-09-17: an uninterned instance at an Id position fails to unify rather than raising). The store is canonical (the same term, the same Id), idempotent and monotone (it only grows and backtracking does not shrink it), so an answer once given is never contradicted. An Id is abstract: only
==, intern/2 and the two boundary predicates look at it.AN ID IS
'__hash_consed_Name/Arity'(Handle)OR'__hash_consed_Name/Arity'(Shape, Handle). Name/Arity is the root constructor of the term and Handle the node handle of one trie, the store. The root is in the functor name so that the rewrite turns a head patternapply(F, X)into an Id pattern that first argument indexing tells apart by constructor, and a value of another constructor, or no Id at all, fails at head unification without a lookup (probe, 2026-09-17: with one functor for every Id, 156 ms against 0.9 ms for 1000 calls over 2000 clauses). The prefix follows SWI-Prolog's convention for names a library synthesises (library(apply_macros)'s'__aux_maplist/N_...'); a leading$is reserved for the system.THE SHAPE is there for clauses told apart by an INNER constructor,
whose heads would otherwise be one Id pattern, every candidate clause paying a lookup before it fails. Shape is ONE ATOM that spells the constructors found below the root, as far as the constructor's template says (user, 2026-09-20: the Id keeps one layer, the shape may have several; the layers are joined into one atom, not kept as several shapes; how deep is configurable). The heads above become
step('__hash_consed_apply/2'('lambda/1', Handle))andstep('__hash_consed_apply/2'('closure_a/1', Handle)), and the deep index SWI-Prolog builds on the first argument of the first argument tells them apart without a lookup (benchmark, 2026-09-20: 1000 calls over 2000 such clauses, 621 ms without the shape and 3 ms with it; upserts 46 percent slower; the store no larger).THE ID STILL HAS ONE LAYER. The shape is not in the functor name, because a shallow pattern
apply(F, X)has to match the Ids of every shape and the set of inner constructors is open across files; and the Id holds no Id of a subterm, because a truncation would give one term two spellings and split==and the table keys. A shape is a function of the content and the template, so the Handle determines it and the store stays canonical.THE CONFIGURATION IS A TEMPLATE, one syntax for every constructor (user, 2026-09-20: one syntax, not three; the template names the constructor, so no indicator beside it; a depth of zero is another feature and is not offered). An element of the directive's list is the constructor applied to one of these at each argument position (a constructor of arity zero is its atom):
_ nothing is recorded about the argument * the root constructor of the argument is recorded a nested template the root constructor is recorded and, when it is the template's constructor, the template goes on below it; alternatives are joined by `;`lambda(_)records the root only and its Ids are'__hash_consed_lambda/1'(Handle).apply(*, _)givesapply(lambda(B), X)the shape'lambda/1'; withapply(apply(*, _), _),apply(apply(lambda(B), Y), X)has the shape'apply/2(lambda/1)'andapply(lambda(B), X)still'lambda/1'. An argument that is no Id is written-. The reading has a precedent in SWI-Prolog's mode-directed tabling,:- table path(_, _, min).THE SHAPE IS A FUNCTION OF THE TERM AND OF ITS OWN TEMPLATE, and of nothing else: at a
*the root is read off the functor name of the argument's Id, with no lookup; each nested template costs an upsert one trie_term/2 on the argument's Id. The spelling of a shape is remembered in a trie keyed by its structure, so an atom is built once per structure; the atoms are finitely many, bounded by the templates and the constructors.AT COMPILE TIME a pattern that determines everything its template records gets the atom written into the head; a pattern that determines only part of it gets an unbound shape, which is correct (the constructors of the pattern are still checked by the lookups of the body) and unindexed. A nested template reads the argument's own arguments off the occurrence of that argument in the same clause, or off the store when its Id was baked. A partial pattern is NOT expanded into one clause per completion: a file loaded later brings new shapes, which the expanded clauses would silently miss. So the deeper a template, the fewer patterns determine it.
ONE CONFIGURATION PER CONSTRUCTOR, PER PROCESS: a second declaration of a constructor with another template raises while the file loads, because two shapes of one term would not be
==(agent design decision, 2026-09-20, not objected to by the user; between files the library checks nothing else, by the user's ruling of 2026-09-17: which files list which constructors is the user's to arrange, for instance with one included file).THE STORE IS ONE TRIE, used through SWI-Prolog's trie API directly (user, 2026-09-17, after a benchmark of four candidate stores: "use the trie"). Its key is the term itself, constructor included (a trie shares a functor node among all its keys), and its value is the key's own handle, written back with trie_update/3 right after the insertion, because inserting an existing key with a different value raises instead of failing. It lives outside the table space and
trie_property(Trie, value_count(Count))counts it.externalized/2 AND internalized/3 ARE THE BOUNDARY: the first replaces every Id by its term, recursively, for printing, for writing a file and for a content address; the second interns, bottom up, the instances of the constructors whose templates it is given, for a term that did not come through the rewrite (a toplevel query, read_term/2, a module that did not opt in, a term built by
=..).:- hash_consing:rewritten(Templates).OPTS A FILE IN (user ruling, 2026-09-17: opt-in per file, the constructors passed to the directive, no setting). Every clause read after it in that file, included files counted, has each occurrence of a listed constructor turned into an Id pattern and a call of intern/2, the direction of each call decided at run time, so the file is written as the program that does not intern and needs no mode declaration. Several directives in one file add up. An empty list rewrites nothing. The library checks no consistency between files: files that pass one constructor to each other must both list it, which the user arranges, for instance with one included file (user ruling, 2026-09-17).THE REWRITE OF A CLAUSE:
head fast path when the Handle of every top-level head occurrence is bound: look them up top down, then run the body, which keeps the last call; otherwise look up the bound ones top down, insert the ground ones bottom up, run the body, insert the rest bottom up (an instantiation error when a term is still not ground) body goal when the variables of its terms are bound: insert bottom up and call; otherwise insert the ground ones, call, and then settle every occurrence top down (a lookup when the call bound the Id, an insertion when it made the term ground, an instantiation error when neither) ground an occurrence that is ground in the source is interned while the file loads and its Id is written into the clauseControl constructs (`,`,
;,->,*->,\+, call/1, forall/2, the goal of findall/3 and of catch/3, a module-qualified goal) are rewritten inside. A listed constructor in the template or the result of findall/3, in the catcher of catch/3, or in a DCG rule raises while the file loads. Directives are not rewritten.TWO RULES FOR A FILE THAT OPTS IN. (1) An instance of a listed constructor is settled, ground or its Id bound, by the end of the goal it is handed to; a term handed over half built and filled in later raises an instantiation error. (2) Reflection sees Ids:
=.., functor/3, arg/3, write/1, variant_sha1/2, term_hash/2 and ordering whose result depends on the order see'__hash_consed_Name/Arity'(Handle). Code that reads structure calls externalized/2 first, and code that builds an instance with them calls internalized/3 after; an instance left uninterned fails to unify at every rewritten position. Using an Id as an opaque key, whose result does not depend on the order (an assoc key, a sort to remove duplicates), is fine.THIS MODULE REFLECTS ON SOURCE CLAUSES with
=..while a file loads, a mechanical boundary; nothing here chooses behaviour at run time by reflection, and no closure is handed to another module.FIXME: trie_term/2 on an integer that is no node of the store crashes the process with a segmentation fault (measured 2026-09-14 on
trie_term(42, _)); a forged'__hash_consed_apply/2'(42)reaches it. Only a bug makes such a value. Candidate fix: none in the trie API; the assertz backends of the benchmark raise instead, at the costs recorded there.FIXME: on 2026-09-14, within tabled compiles near a full stack, trie_lookup/3 failed silently on a key of the store and left the resource error pending. Here such a failure turns an upsert into an insertion of an existing key, which raises, or a lookup into a failure, which reads as no answer rather than
resource_error. A direct probe at 1m, 4m and 32m stacks on 2026-09-17 raisedresource_error(stack)every time and did not reproduce it.FIXME: the order of two Ids depends on the insertion history. Nothing checks that no code in an opted-in file reads that order; it is a rule of the file, stated above.
FIXME: an Id written into a clause while the file loads is a handle of this process. A file must be loaded from source, never
qcompiled nor saved in a state, and the store lives in one thread's global variable.FIXME: a file that uses a constructor another file interns, without listing it, passes its instances uninterned; the rewritten positions of the other file then fail to unify, silently. The library cannot see it.