In 7.0.0 a named fact is a deftemplate inheriting from ND, which brings the whole inheritance mechanism with it. The part that is wanted here is a symbol-keyed table from name to fact, a generator for unnamed facts, and acceptance of a name wherever a fact address is accepted. None of that needs inheritance.
What to check when building it: that two facts cannot take the same name; that the name survives a modify, which retracts and re-asserts under the same index; and that a retracted name becomes available again, or does not, as a deliberate decision rather than an accident of when the table is cleared.
7.0.0 puts the name in a slot and then exempts that slot inside HandleFactDuplication. Its default name is @f-1, from the fact index, which modify reuses and a later assert can take, so the scheme needs @f-1-1 when a default is already taken. And a symbol beginning with @ means the fact of that name when passed to a generic function, which changes how every symbol reads.
Here the name is metadata rather than a slot, it comes from a counter that is never rewound, and the positions that take one say so in their own argument restrictions.
A fact index changes. (modify ?f …) keeps it, but (reset) and (clear) start again from 1, so an index written down outside the engine may later name a different fact. A name does not: once minted it is never given to another fact in the session.
Not part of the fact. HashFact hashes all of a fact's slots, so a name in a slot would take part in duplicate detection — and two facts alike but for their names would be two facts. The name is held beside the fact index and the hash, outside the proposition. So a named fact and an unnamed one of the same content are still one fact:
(fact-name ?one) -> _1
(assert (claim (subject sky) (says blue))) -> <Fact-1>
(length$ (find-all-facts ((?f claim)) TRUE)) -> 1
Not reachable by modify. Being outside the slots, there is no (modify ?f (name something-else)). set-fact-name is the only way to change one.
Not lost to a modify. modify retracts a fact and asserts it again under the same index, and the name comes through:
the name before a modify -> _1
the name after it -> _1
the index after it -> 1
Where a modify's result is a fact that already exists and duplicate detection is on, the modify answers that existing fact — and the name moves to it, unless that fact already has one of its own, in which case it keeps it and the other name goes the way of its fact.
fact-name — the name, minted on the first asking(fact-name <fact-address-or-index-or-name>)
(deftemplate claim (slot subject) (slot says))
(bind ?one (assert (claim (subject sky) (says blue))))
(fact-name ?one) -> _1
(fact-name ?one) -> _1
A fact has no name until something asks for one, so a program that never names a fact makes no symbols for it. The names run _1, _2, … from a counter.
A retracted fact is reported, as it is by fact-slot-value and the rest:
(fact-name ?one)
[PRNTUTIL11] The fact f-1 has been retracted.
fact-named is the way to ask about a name without that, and answers FALSE.
The counter is not the fact index. It counts names asked for, not facts asserted, and it is never rewound:
the third fact's index -> 3
the first name asked for -> _1
fact-named — the fact of that name(fact-named <symbol>)
Answers the fact, or FALSE where no fact has that name.
(fact-named _1) -> <Fact-1>
(fact-named never-minted) -> FALSE
A retracted fact's name answers FALSE and goes on doing so. It is not reissued:
the name before a retract -> _1
after the retract -> FALSE
the name the next fact gets -> _2
set-fact-name — a name the program chose(set-fact-name <fact-address-or-index-or-name> <symbol>)
(set-fact-name ?one weather) -> TRUE
(fact-name ?one) -> weather
(fact-named weather) -> <Fact-1>
Giving a fact the name it already has answers TRUE and does nothing. Renaming is allowed, and the name given up resolves to nothing afterwards.
A name another fact holds is refused, and the refusal names both:
[FACTCOM1] The name weather is already f-1's, so f-2 cannot take it.
The shape the engine mints is reserved. An underscore followed by digits and nothing else:
(set-fact-name ?one _7)
[FACTCOM2] The name _7 has the shape this engine mints for itself, an underscore and digits, which is reserved so that no name is ever given to two facts.
(set-fact-name ?one _about_sky) -> TRUE
(set-fact-name ?one _) -> TRUE
(set-fact-name ?one fact7) -> TRUE
Without the reservation a program could take _7, retract the fact, and the counter would hand _7 out again on reaching 7.
An underscore rather than the word fact, so the reservation costs nothing a program would want: fact7 is a name somebody might choose, _7 is not. An underscore anywhere else in a name is yours, and so is the bare underscore, which has no digits after it.
A name gets written down outside the engine — into the graph, a file, a command line — and it has to be readable back. So a name may hold any character except these twenty:
" ' ( ) & ; | \ / { } ~ ` [ ] < > * space tab
and no ASCII control character, and it may not be empty.
The asterisk is on the list for a second reason: (retract *) means every fact, so no fact may be named *.
(set-fact-name ?one (sym-cat a " " b))
[FACTCOM3] The name 'a b' cannot be a fact name because it holds a space.
(set-fact-name ?one (sym-cat a "&" b))
[FACTCOM3] The name 'a&b' cannot be a fact name because it holds an ampersand.
(set-fact-name ?one (sym-cat a (format nil "%c" 7) b))
[FACTCOM3] The name 'ab' cannot be a fact name because it holds a control character.
(set-fact-name ?one (sym-cat ""))
[FACTCOM3] The name '' cannot be a fact name because it has no characters.
The refusal names the character rather than showing it, because a space and a tab both read as nothing in a message. The name is quoted with a single quote, which is safe because a single quote is one of the twenty.
The reader has the last word. Beyond the list, a name is refused if CLIPS would not read it back as that very symbol. That is asked of the scanner rather than of the characters, so what counts as a symbol here and in a command typed at the prompt is one answer:
(set-fact-name ?one (sym-cat "7"))
[FACTCOM3] The name '7' cannot be a fact name because CLIPS would not read it back as that symbol.
(set-fact-name ?one (sym-cat "?x"))
[FACTCOM3] The name '?x' cannot be a fact name because CLIPS would not read it back as that symbol.
7 would come back an integer, 1.5 a float, ?x a variable and $?x a multifield variable.
Letters, digits and most punctuation are a program's to use:
(set-fact-name ?one weather) -> TRUE
(set-fact-name ?one WEATHER) -> TRUE
(set-fact-name ?one WeatherStation) -> TRUE
(set-fact-name ?one station7) -> TRUE
(set-fact-name ?one claim.7:a-b_c) -> TRUE
So is every character outside ASCII. Each of the twenty is an ASCII character and the control range is ASCII, and no byte of a UTF-8 multi-byte sequence falls below 0x80, so no part of a character can be mistaken for one of them. What such a name must satisfy instead is below.
A name reaching past ASCII is checked against Unicode, which reads the Unicode Character Database once in the session — 76 milliseconds, and never for a program whose names are ASCII.
A cluster is what a reader sees, not a code point. A flag is two code points and one cluster; a family joined with zero width joiners is seven and one; a raised thumb with a skin tone is two and one. All of them are names:
(set-fact-name ?one 🇯🇵) -> TRUE
(set-fact-name ?one 👨👩👧👦) -> TRUE
(set-fact-name ?one 👍🏽) -> TRUE
(set-fact-name ?one 한국어) -> TRUE
(set-fact-name ?one नमस्ते) -> TRUE
One normalisation form, because two that look alike would be two names. café composed and cafe with a combining acute are different symbols that render identically. Allowing both would let two facts hold handles nobody could tell apart, which is the first thing a name is for, so NFC is required:
(set-fact-name ?one (sym-cat "cafe" (unicode-character 769)))
[FACTCOM3] The name 'café' cannot be a fact name because it is not in Unicode normalisation form NFC; (unicode-normalize nfc …) gives the form to use.
The form is required rather than applied, because (set-fact-name ?f X) answering TRUE and then (fact-name ?f) answering something other than X would be worse than being told.
A name may not begin with a combining mark, which is a cluster with nothing before it to combine with:
(set-fact-name ?one (sym-cat (unicode-character 769) "abc"))
[FACTCOM3] The name '́abc' cannot be a fact name because it begins with a combining mark, which has nothing before it to combine with.
No control character, wherever it sits. The ASCII ones are on the list above; the C1 block is not visible to a scan over bytes, because U+0091 encodes as C2 91 and neither byte is below 0x80:
(set-fact-name ?one (sym-cat (unicode-character 145) "1"))
[FACTCOM3] The name '1' cannot be a fact name because it holds a control character.
The name in that message is U+0091 followed by 1, and it prints as 1 — which is the reason to refuse it. A handle you cannot tell from the number 1 is no handle, and 0x91 is a quotation mark in CP1252, so one that passes through a legacy codepage comes back as '.
Something in it has to show. A name of joiners and selectors is a name nobody can see, which is the objection to the empty name arriving in another costume. At least one character must be a letter, a number, a punctuation mark or a symbol — a mark, a format control and a separator each show nothing on their own:
(set-fact-name ?one (sym-cat (unicode-character 8205)))
[FACTCOM3] The name '' cannot be a fact name because nothing in it shows: every character is a mark, a format control or a separator.
A zero width joiner, a variation selector, a tag character, a soft hyphen, a byte order mark, a word joiner, a no-break space and a left-to-right mark are each refused alone. Beside something visible they are fine, which is what keeps 👨👩👧👦 and 👍🏽 usable:
(set-fact-name ?one (sym-cat "a" (unicode-character 8205))) -> TRUE
(set-fact-name ?one (sym-cat (unicode-character 8205) "a")) -> TRUE
The test is the general category, asked of the library. U+3164 HANGUL FILLER and its three relatives are taken, because Unicode calls them letters even though they render blank — that is the line the standard draws, and drawing another would mean keeping a list of exceptions the library could not confirm.
And the bytes must be well-formed UTF-8. A lone continuation byte, a lead byte with nothing following it, and an overlong encoding are each refused:
(set-fact-name ?one (sym-cat "a" (format nil "%c" 128) "b"))
[FACTCOM3] The name '…' cannot be a fact name because it is not well-formed UTF-8.
The message holds the name as given, so a name of malformed bytes puts malformed bytes on the error stream. That is also why the suite for this reads its output with byte-oriented tools: sed refuses a stream holding an invalid sequence, and grep decides the whole stream is binary and matches nothing without -a.
Nothing segments the name, because UAX #29 partitions any sequence of code points: a name that decodes always divides into clusters, so a segmentation pass could refuse none. Eighteen cluster-edge names were tried against one — regional indicators alone, in pairs and in threes, a zero width joiner alone and between letters, Hangul jamo, a variation selector, a tag character, a Devanagari virama, a prepend mark, the last code point — and it fired for no name among them. Dropping it took a thirteen-character name from 8.37 µs to 1.71 µs.
Anywhere a function takes a fact and nothing else:
(fact-index weather) -> 1
(fact-existp weather) -> TRUE
(fact-relation weather) -> claim
(fact-slot-value weather says) -> blue
(fact-slot-names weather) -> (subject says)
(modify weather (says grey)) -> <Fact-1>
(duplicate weather (subject sea))
(retract weather)
ppfact writes to a router rather than answering:
(ppfact weather) -> (claim
(subject sky)
(says blue))
dependencies, dependents, get-dependency-list, get-dependency-count and get-dependent-list do not take a name. Those accept an instance as well as a fact, and a symbol there is already an instance name. Pass the fact or its index.
A name no fact has is reported where a fact was wanted:
(fact-slot-value nothing-named-this says)
[PRNTUTIL1] Unable to find fact nothing-named-this.
and what these functions refused before, they still refuse:
(fact-existp "sky")
[ARGACCES2] Function 'fact-existp' expected argument #1 to be of type integer, symbol, or fact address.