Wir entwerfen eine Multi-Paradigma-Programmiersprache. Teil 5 - Funktionen der Logikprogrammierung

Wir setzen die Geschichte über die Schaffung einer Programmiersprache mit mehreren Paradigmen fort, die einen deklarativen logischen Stil mit einem objektorientierten und funktionalen Stil kombiniert. Dies wäre praktisch, wenn Sie mit halbstrukturierten Daten arbeiten und Daten aus unterschiedlichen Quellen integrieren. Die Sprache besteht aus zwei Komponenten, die eng miteinander verbunden sind: Die deklarative Komponente ist für die Beschreibung des Domänenmodells verantwortlich, und die imperative oder funktionale Komponente ist für die Beschreibung der Algorithmen für die Arbeit mit dem Modell und die Berechnung verantwortlich.



Im letzten ArtikelIch begann meine Geschichte über die hybride Sprachmodellierungskomponente. Es ist eine Reihe von Konzeptobjekten, die durch logische Beziehungen verbunden sind. Es gelang mir, über die wichtigsten Möglichkeiten zu sprechen, Konzepte, Vererbung und die Beziehung zwischen ihnen zu definieren. Die Gründe, die mich dazu veranlasst haben, eine hybride Sprache und ihre Funktionen zu entwerfen, sind in meinen früheren Veröffentlichungen zu diesem Thema zu finden. Links zu ihnen finden Sie am Ende dieses Artikels.



Und jetzt schlage ich vor, in einige der Nuancen der Logikprogrammierung einzutauchen. Da die Sprache der Modellierungskomponente eine deklarative logische Form hat, müssen Probleme wie das Definieren der Semantik des Negationsoperators, das Einführen von Elementen höherer Logik und das Hinzufügen der Fähigkeit zum Arbeiten mit logischen Variablen gelöst werden. Und um dies zu tun, müssen Sie sich mit theoretischen Fragen wie der Annahme der Offenheit / Geschlossenheit der Welt, der Verweigerung als Ablehnung, der stabilen Modellsemantik und der fundierten Semantik befassen. Und auch damit, wie die Möglichkeiten der Logik höherer Ordnung in anderen logischen Programmiersprachen implementiert werden.



Beginnen wir mit booleschen Variablen



In den meisten logischen Programmiersprachen werden Variablen als symbolische Bezeichnung (Platzhalter) für beliebige Anweisungen verwendet. Sie treten an den Positionen der Argumente von Prädikaten auf und verbinden die Prädikate miteinander. In der folgenden Regel der Prolog-Sprache spielen Variablen beispielsweise die Rolle der Objekte X und Y , die durch Beziehungen verbunden sind: Bruder , Eltern , Mann und Ungleichung:



brothers(X,Y) :- parent(Z,X), parent(Z,Y), male(X), male(Y), X\=Y.
      
      





In der Modellierungskomponente wird die Rolle von Begriffsargumenten hauptsächlich von Konzeptattributen gespielt:



concept brothers (person1, person2) FROM
parent p1 (child = person1),
parent p2 (child = person2),
male(person: person1),
male(person: person2)
WHERE p1.parent = p2.parent AND person1 != person2
      
      





Sie können wie in SQL direkt über den Namen aufgerufen werden. Obwohl die vorgeschlagene Syntax im Vergleich zu Prolog umständlicher erscheint, ist diese Option bei einer großen Anzahl von Attributen bequemer, da sie die Struktur des Objekts hervorhebt.



In einigen Fällen wäre es jedoch immer noch zweckmäßig, eine boolesche Variable zu deklarieren, die nicht in den Attributen eines der Konzepte enthalten ist, sondern gleichzeitig für den Ausdruck von Beziehungen verwendet wird. Wenn ein Unterausdruck beispielsweise eine komplexe Form hat, können Sie ihn in seine Bestandteile zerlegen, indem Sie sie mit booleschen Variablen verknüpfen. Wenn ein Unterausdruck mehrmals verwendet wird, können Sie ihn nur einmal deklarieren, indem Sie ihn einer Variablen zuordnen. Verwenden Sie dann eine Variable anstelle eines Ausdrucks. Um boolesche Variablen von Konzeptattributen und Variablen für Berechnungskomponenten zu unterscheiden, müssen wir entscheiden, dass boolesche Variablennamen mit dem Symbol $ beginnen müssen .

Als Beispiel können wir das Konzept analysieren, das die Zugehörigkeit eines Punktes zu einem Ring definiert, der durch die äußeren und inneren Radien beschrieben wird. Der Abstand von einem Punkt zur Mitte eines Rings kann einmal berechnet, mit einer Variablen verknüpft und mit den Radien verglichen werden:



relation pointInRing between point p, ring r 
where $dist <= r.rOuter 
    and $dist >= r.rInner 
    and $dist = Math.sqrt((p.x – r.x) * (p.x – r.x) + (p.y – r.y) * (p.y – r.y))
      
      





Gleichzeitig spielt diese Distanz selbst eine Hilfsrolle und wird nicht Teil des Konzepts sein.



Negation.



Betrachten wir nun ein komplexeres Problem - die Implementierung des Negationsoperators innerhalb der Modellierungskomponente. Logikprogrammiersysteme enthalten normalerweise zusätzlich zum Operator der booleschen Negation die Negationsregel als Ablehnung. Sie können nicht p drucken , wenn die Ableitung von p fehlschlägt . In verschiedenen Systemen der Wissensrepräsentation können sowohl die Bedeutung als auch der Algorithmus der Regel der Inferenz der Negation unterschiedlich sein.



Zunächst muss die Frage nach der Natur des Wissenssystems hinsichtlich seiner Vollständigkeit beantwortet werden. In Systemen, die der "Annahme der offenen Welt" entsprechen , wird die Wissensbasis als unvollständig angesehen, sodass Aussagen, die darin fehlen, als unbekannt angesehen werden. Keine Behauptung kann nur ausgegeben werden, wenn die Wissensdatenbank explizit die Anweisung speichert, dass pfalsch. Eine solche Ablehnung wird als stark bezeichnet. Fehlende Aussagen gelten als unbekannt, nicht als falsch. Ein Beispiel für ein Wissenssystem, das eine solche Annahme verwendet, ist das semantische WEB. Es ist ein öffentlich zugängliches globales semantisches Netzwerk, das auf der Grundlage des World Wide Web gebildet wird. Die darin enthaltenen Informationen sind per Definition unvollständig - sie sind nicht vollständig digitalisiert und in eine maschinenlesbare Form übersetzt, sie sind auf verschiedene Knoten verteilt und werden ständig ergänzt. Wenn beispielsweise in Wikipedia in einem Artikel über Tim Berners-Lee, den Schöpfer des World Wide Web und Autor des Konzepts des Semantic Web, nichts über seine kulinarischen Vorlieben gesagt wird, bedeutet dies nicht, dass er sie nicht hat, der Artikel ist einfach unvollständig.



Die entgegengesetzte Annahme ist "die Annahme, dass die Welt geschlossen ist".... Es wird angenommen, dass in solchen Systemen die Wissensbasis vollständig ist und die darin enthaltenen fehlenden Aussagen als nicht existent oder falsch angesehen werden. Die meisten Datenbanken folgen dieser Annahme. Wenn die Datenbank keinen Datensatz über eine Transaktion oder über einen Benutzer enthält, sind wir sicher, dass eine solche Transaktion nicht vorhanden war und der Benutzer nicht im System registriert ist (zumindest basiert die gesamte Logik des Systems auf der Tatsache, dass sie nicht vorhanden sind).



Komplette Wissensdatenbanken haben ihre Vorteile. Erstens ist es nicht erforderlich, unbekannte Informationen zu codieren - zweiwertige Logik (wahr, falsch) ist ausreichend anstelle von dreiwertiger (wahr, falsch, unbekannt). Zweitens können Sie den Booleschen Negationsoperator und die Überprüfung der Ableitbarkeit einer Anweisung aus der Wissensbasis als Ablehnung in einen Negationsoperator kombinieren (Verneinung als Misserfolg). Es wird nicht nur true zurückgegeben, wenn die Aussage, dass die Aussage falsch ist, gespeichert ist, sondern auch, wenn die Wissensdatenbank überhaupt keine Informationen darüber enthält. Zum Beispiel wird die

Regel



p <— not q





schließen, dass q falsch ist (da es keine Aussage gibt, dass es wahr ist) und p wahr ist.



Leider ist die Semantik der Verleugnung als Ablehnung nicht so offensichtlich und komplexer als sie scheint. In verschiedenen logischen Programmiersystemen hat seine Bedeutung seine eigenen Eigenschaften. Zum Beispiel bei zyklischen Definitionen:



p ← not q
q ← not p
      
      





Die in der Prolog-Sprache verwendete klassische SLDNF-Auflösung (SLD + Negation als Fehler) schlägt fehl. Die Ausgabe der Anweisung p benötigt die Ausgabe von q , und q - in p fällt die Ausgabeprozedur in eine Endlosschleife. In Prolog werden solche Definitionen als ungültig und die Wissensbasis als inkonsistent angesehen.



Gleichzeitig sind diese Aussagen für uns kein Problem. Intuitiv verstehen wir diese beiden Regeln so, dass p und q gesagt werden haben entgegengesetzte Bedeutungen, wenn einer von ihnen wahr ist, dann ist der andere falsch. Daher ist es wünschenswert, dass der Operator der Negation als Ablehnung in der Lage ist, mit solchen Regeln zu arbeiten, damit die Konstruktionen von Logikprogrammen für den Menschen natürlicher und verständlicher sind.



Auch Konsistenz in der Wissensbasis ist nicht immer erreichbar. Beispielsweise werden Regeldefinitionen manchmal absichtlich von Fakten getrennt, sodass derselbe Regelsatz auf verschiedene Faktensätze angewendet werden kann. In diesem Fall kann nicht garantiert werden, dass die Regeln mit allen möglichen Fakten übereinstimmen. Manchmal ist es auch akzeptabel, dass die Regeln selbst inkonsistent sind, beispielsweise wenn sie von verschiedenen Experten erstellt werden.



Die bekanntesten Kampagnen, die es ermöglichen, die logische Schlussfolgerung unter zyklischen Definitionen und Programminkonsistenzen zu formalisieren, sind "Semantik nachhaltig" (stabile Modellsemantik) und "vernünftige Semantik" (die Semantik begründet).



Die Inferenzregel mit persistenter Modellsemantik basiert auf der Annahme, dass einige Negationsoperatoren in einem Programm ignoriert werden können, wenn sie nicht mit dem Rest des Programms übereinstimmen. Da es mehrere solcher konsistenten Teilmengen des anfänglichen Regelsatzes geben kann, kann es jeweils mehrere Lösungen geben. In der obigen Definition kann die Inferenz beispielsweise mit der ersten Regel beginnen ( p ← nicht q ), verwerfe die zweite ( q ← nicht p ) und erhalte die Lösung {p, nicht q} . Und dann mache dasselbe für die Sekunde und erhalte {q, nicht p} . Die Gesamtlösung wäre ein kombinierter Satz alternativer Lösungen. Zum Beispiel aus den Regeln:



person(alex)
alive(X) ←person(X)
male(X) ←person(X) AND NOT female(X)
female(X) ←person(X) AND NOT male(X)
      
      





Wir können zwei Antwortoptionen anzeigen: {Person (Alex), lebendig (Alex), männlich (Alex)} und {Person (Alex), lebendig (Alex), weiblich (Alex)} .

Eine vernünftige Semantik beginnt mit denselben Annahmen, sucht jedoch nach einer allgemeinen Teillösung, die alle alternativen einvernehmlichen Teilmengen von Regeln erfüllt. Teillösung bedeutet, dass die Werte "wahr" oder "falsch" nur für einen Teil der Fakten angezeigt werden und die Werte des Restes unbekannt bleiben. Daher wird bei der Beschreibung der Fakten im Programm eine zweiwertige Logik und im Inferenzprozess eine dreiwertige verwendet. Für die oben betrachteten Regeln gelten die Bedeutungen von p und qwird unbekannt sein. Aber zum Beispiel für



p ← not q
q ←not p
r ← s
s
      
      





wir können sicher schließen, dass r und s wahr sind, obwohl p und q unbekannt bleiben.



Zum Beispiel könnten wir aus dem Alex-Beispiel {person (alex), living (alex)} ableiten , während die Aussagen männlich (alex) und weiblich {alex} unbekannt bleiben.



In SQL werden die boolesche Negation ( NOT ) und die Ableitbarkeitsprüfung ( NOT EXISTS ) überprüft ) getrennt sein. Diese Operatoren gelten für verschiedene Arten von Argumenten: NICHT invertiert einen booleschen Wert, und EXISTS / NOT EXISTS überprüft das Ergebnis einer verschachtelten Abfrage auf Leere, sodass es keinen Sinn macht, sie zu kombinieren. Die Semantik der Negationsoperatoren in SQL ist sehr einfach und nicht für rekursive oder komplexe inkonsistente Abfragen ausgelegt. Mit einer speziellen SQL-Fähigkeit kann die Abfrage an eine unendliche Rekursion gesendet werden. Komplexe logische Konstrukte liegen jedoch eindeutig außerhalb des Bereichs herkömmlicher SQL-Anwendungen, sodass keine ausgefeilte Semantik für Negationsoperatoren erforderlich ist.



Versuchen wir nun, die Semantik der Negationsoperatoren der Modellierungskomponente der entworfenen Hybridsprache zu verstehen .



Erstens ist die Modellierungskomponente so konzipiert, dass unterschiedliche Datenquellen integriert werden. Sie können sehr unterschiedlich sein und entweder vollständig oder unvollständig sein. Daher werden auf jeden Fall Operatoren zur Ableitbarkeitsprüfung benötigt.



Zweitens ist die Form der Modellierung von Komponentenkonzepten SQL-Abfragen viel näher als logischen Programmierregeln. Das Konzept hat auch eine komplexe Struktur, so dass es nicht sinnvoll ist, einen Operator der Booleschen Negation einzumischen und die Ableitbarkeit des Konzepts zu überprüfen. Die boolesche Negation ist nur für Attribute, Variablen und Ausdrucksergebnisse sinnvoll - sie können entweder falsch oder wahr sein. Es ist schwieriger, es auf ein Konzept anzuwenden, es kann aus verschiedenen Attributen bestehen und es ist nicht klar, welche von ihnen für die Falschheit oder Wahrheit des Konzepts als Ganzes verantwortlich sein sollten. Das Konzept kann aus den Anfangsdaten als Ganzes und nicht aus den einzelnen Attributen abgeleitet werden. Im Gegensatz zu SQL, wo die Struktur von Tabellen festgelegt ist, kann die Struktur von Konzepten flexibel sein. Ein Konzept hat möglicherweise überhaupt nicht das erforderliche Attribut in seiner Zusammensetzung.Daher müssen Sie auch die Existenz des Attributs überprüfen.



Daher ist es sinnvoll, für jeden oben aufgeführten Negationstyp separate Operatoren einzuführen. Die Falschheit von Attributen kann mit dem herkömmlichen booleschen NOT- Operator überprüft werden , ob ein Konzept ein Attribut mit der integrierten Funktion DEFINED enthält und das Ergebnis der Ableitung des Konzepts aus den Originaldaten mit der Funktion EXISTS . Die drei separaten Operatoren sind vorhersehbarer, verständlicher und einfacher zu verwenden als der komplexe Negation-as-Failure-Operator. Bei Bedarf können sie auf die eine oder andere Weise zu einem Operator kombiniert werden, was für jeden Einzelfall sinnvoll ist.



Drittens wird die Modellierungskomponente derzeit als Werkzeug zum Erstellen kleiner Ontologien auf Anwendungsebene angesehen. Es ist unwahrscheinlich, dass seine Sprache eine besondere logische Ausdruckskraft und ausgefeilte Inferenzregeln benötigt, die mit rekursiven Definitionen und logischen Inkonsistenzen des Programms umgehen können. Daher erscheint die Implementierung komplexer Inferenzregeln, die auf der Semantik persistenter Modelle oder der geerdeten Semantik basieren, zumindest in dieser Phase nicht ratsam. Die klassische SLDNF-Auflösung sollte ausreichen.



Schauen wir uns nun einige Beispiele an.



Ein Konzept kann eine negative Konnotation erhalten, wenn bestimmte seiner Attribute eine Bedeutung haben, die es anzeigt. Durch die Negation von Attributen können Sie solche Entitäten explizit finden:



concept unfinishedTask is task t where not t.completed
      
      





Die Funktion zum Überprüfen der Mehrdeutigkeit eines Attributs ist praktisch, wenn die Entitäten eines Konzepts unterschiedliche Strukturen haben können:



concept unassignedTask is task t where not defined(t.assignedTo) or empty(t.assignedTo)
      
      





Die Funktion zur Überprüfung der Ableitbarkeit eines Konzepts ist bei der Arbeit mit rekursiven Definitionen und hierarchischen Strukturen unersetzlich:



concept minimalElement is element greater 
where not exists(element lesser where greater.value > lesser.value)
      
      





In diesem Beispiel wird die Überprüfung auf das Vorhandensein eines kleineren Elements als Unterabfrage durchgeführt. Ich plane, die Erstellung verschachtelter Abfragen in der nächsten Veröffentlichung im Detail zu betrachten.



Elemente höherer Logik



In der Logik erster Ordnung können Variablen nur mit Objektgruppen übereinstimmen und erscheinen nur an den Positionen der Argumente von Prädikaten. In der Logik höherer Ordnung können sie auch Mengen von Beziehungen entsprechen und an der Position von Prädikatnamen erscheinen. Mit anderen Worten, die Logik erster Ordnung erlaubt es uns, die Behauptung aufzustellen, dass eine Beziehung für alle oder einige der Objekte wahr ist. Und eine Logik höherer Ordnung besteht darin, die Beziehung zwischen Beziehungen zu beschreiben.



Zum Beispiel können wir Aussagen machen, dass einige Leute Brüder, Schwestern, Kinder oder Eltern, Onkel oder Tanten usw. Sind:



Brother(John, Joe).
Son(John, Fred).
Uncle(John, Alex).
      
      





Um jedoch eine Beziehungsaussage zu machen, müssen wir in der Logik erster Ordnung alle obigen Anweisungen auflisten und sie mit der ODER-Verknüpfung kombinieren:



ⱯX,ⱯY(Brother(X, Y) OR Brother(Y, X) OR Son(X, Y) OR Son(Y, X) OR Uncle(X, Y) OR Uncle(Y, X) → Relative(X, Y)).
      
      





Mit der Logik zweiter Ordnung können Sie eine Aussage über andere Aussagen machen. Zum Beispiel könnte man ausdrücklich sagen, dass die Beziehung zwischen Geschwistern, Eltern und Kindern, Onkeln und Neffen eine Verwandtschaftsbeziehung ist:



RelativeRel(Brother).
RelativeRel(Son).
RelativeRel(Uncle).
ⱯX,ⱯY(ⱻR(RelativeRel(R) AND (R(X, Y) OR R(Y, X))) → Relative(X, Y)).
      
      





Wir argumentieren, dass wenn für jedes X und Y eine Beziehung R eine Beziehung zwischen RelativeRel- Geschwistern ist und X und Y diese Beziehung erfüllen, X und Y Geschwister sind. Beziehungsargumente können andere Beziehungen sein, und Beziehungsnamen können durch Variablen ersetzt werden.



Mit der Logik dritter Ordnung können Sie Anweisungen über Anweisungen über Anweisungen usw. erstellen. Logik höherer Ordnung- über Aussagen einer beliebigen Verschachtelungsebene. Logik höherer Ordnung ist viel ausdrucksvoller, aber auch viel komplexer. In der Praxis unterstützen logische Programmiersysteme nur einige ihrer Elemente, die hauptsächlich auf die Verwendung von Variablen und beliebigen Ausdrücken an den Positionen von Prädikaten beschränkt sind.



In Prolog werden die Elemente einer solchen Logik unter Verwendung mehrerer integrierter Meta-Prädikate implementiert, deren Argumente andere Prädikate sind. Die wichtigste davon ist das Prädikat AufrufHiermit können Sie der Zielliste der aktuellen Regel dynamisch Prädikate hinzufügen. Sein erstes Argument wird als Ziel behandelt, der Rest als Argument. Prolog durchsucht die Wissensdatenbank nach Prädikaten, die mit dem ersten Argument übereinstimmen, und fügt sie der aktuellen Zielliste hinzu. Ein Beispiel mit Verwandten würde so aussehen:



brother(john, jack).
sister(mary, john).
relative_rel(brother).
relative_rel(sister).
relative(X, Y) :- relative_rel(R), (call(R, X, Y); call(R, Y, X)).
      
      





Prolog unterstützt auch Prädikate findall (Template, Tor, Beutel) , bagof (Vorlage, Tor, Beutel) , SETOF (Template, Tor, Set) , usw., mit denen Sie alle finden Goal Ziel - Lösungen , die das Spiel Template - Vorlage und unify (link) ihre Liste mit dem Ergebnis Bag (oder Set ). Prolog verfügt über integrierte Prädikate current_predicate , Klausel und andere, um Prädikate in der Wissensdatenbank zu finden. Sie können Prädikate und ihre Attribute auch in Wissensdatenbanken bearbeiten - hinzufügen, löschen und kopieren.



Die HiLog- Sprache unterstützt Logik höherer Ordnung auf Syntaxebene . Anstelle spezieller Meta-Prädikate können beliebige Begriffe (z. B. Variablen) direkt an der Position von Prädikatnamen verwendet werden. Die Regel zur Bestimmung der Verwandten hat folgende Form:



relative(X, Y) :- relative_rel(R), (R(X, Y); R(Y, X)).
      
      





Diese Syntax ist im Vergleich zu Prolog deklarativer, prägnanter, verständlicher und natürlicher. Gleichzeitig bleibt HiLog eine syntaktische Variante von Prolog, da alle syntaktischen HiLog-Konstrukte mithilfe der Aufruf- Meta-Prädikate in logische Ausdrücke erster Ordnung konvertiert werden können .



HiLog hat eine Syntax höherer Ordnung , aber eine Semantik erster Ordnung . Dies bedeutet, dass beim Vergleich von Variablen, die Regeln oder Funktionen darstellen, nur deren Namen berücksichtigt werden, nicht deren Implementierung. Es gibt auch Sprachen, die Semantik höherer Ordnung unterstützen, wie z. B. λ-Prolog, die es auch ermöglichen, Regeln und Funktionen in den Inferenzprozess einzubeziehen. Eine solche Logik und ihre Inferenzalgorithmen sind jedoch viel komplizierter.



Kommen wir nun zur Logikfunktionalität höherer Ordnung der Modellierungskomponente . Für die meisten praktischen Meta-Programmieraufgaben sollten Prolog und HiLog ausreichend sein. HiLog hat eine natürlichere Syntax, daher ist es sinnvoll, diese als Grundlage zu verwenden. Um beliebige Ausdrücke für die Positionen der Namen von Konzepten und deren Attribute verwenden und sie von Variablen, Funktionsaufrufen und anderen Konstruktionen unterscheiden zu können, führen wir einen speziellen Operator zur dynamischen Angabe von Namen ein :

< >







Sie können den Wert eines Ausdrucks auswerten und ihn je nach Kontext als Konzeptnamen, Alias ​​oder Attributnamen verwenden. Wenn dieser Operator anstelle des Konzeptnamens im Abschnitt FROM steht und der Wert seines Ausdrucks definiert ist, werden alle Konzepte mit dem angegebenen Namen gefunden und eine logische Suche für sie durchgeführt:

concept someConcept ( … ) from conceptA a, <a.conceptName> b where …





Wenn der Wert des Ausdrucks beispielsweise nicht definiert ist, ist der Ausdruck eine boolesche Variable, die dem Wert nicht zugeordnet ist Dann findet die Prozedur alle geeigneten Konzepte und ordnet den Wert der Variablen ihren Namen zu:

concept someConcept is <$conceptName> where …





Wir können sagen, dass der Operator zur Angabe des Namens im Kontext des FROM- Abschnitts eine logische Semantik hat .

Außerdem kann der Operator <> und die WHERE-Klauseln an der Position eines Konzeptalias oder Attributnamens verwendet werden:



concept someConcept ( … ) from conceptA a, conceptB b where conceptB.<a.foreignKey> = a.value ...
      
      





Ausdrücke in der WHERE-Klausel sind deterministisch, dh sie verwenden keine logische Suche, um unbekannte Werte für ihre Argumente zu finden. Dies bedeutet, dass der Ausdruck conceptB. <A.foreignKey> = a.value erst ausgewertet wird, nachdem die Entitäten des Konzepts a gefunden wurden und seine Attribute fremder Schlüssel und Wert Werten zugeordnet sind. Daher können wir sagen, dass die name-Anweisung im Kontext der FROM-Klausel eine funktionale Semantik hat .



Betrachten wir einige mögliche Anwendungen der Logik höherer Ordnung.

Das offensichtlichste Beispiel, bei dem Logik höherer Ordnung zweckmäßig ist, ist die Vereinigung aller Konzepte unter einem Namen, die bestimmte Bedingungen erfüllen. Zum Beispiel mit bestimmten Attributen. Das Konzept des Punktes kann also als alle Konzepte betrachtet werden, die die Koordinaten x und y enthalten :



concept point is <$anyConcept> a where defined(a.x) and defined(a.y)
      
      





Die boolesche Suche bindet die Variable $ anyConcept mit allen deklarierten Konzeptnamen (außer natürlich selbst), die Koordinatenattribute haben.



Ein komplexeres Beispiel wäre die Deklaration einer allgemeinen Beziehung, die für viele Konzepte gilt. Zum Beispiel eine transitive Eltern-Kind-Beziehung zwischen Konzepten:



relation ParentRel between <$conceptName> parent, <$conceptName> child
where defined(parent.id) and defined(child.parent) and (
parent.id = child.parent or exists(
	<$conceptName> intermediate where intermediate.parent = parent.id 
            and ParentRel(intermediate, child)	
))
      
      





Die Variable $ conceptName wird in allen drei Konzepten von Parent, Child und Intermediate verwendet. Dies bedeutet, dass ihre Namen identisch sein müssen.



Logik höherer Ordnung eröffnet Möglichkeiten für die generische Programmierung in dem Sinne, dass Sie generische Konzepte und Beziehungen erstellen können, die auf viele Konzepte angewendet werden können, die bestimmte Bedingungen erfüllen, ohne an ihre spezifischen Namen gebunden zu sein.



Eine dynamische Namensersetzung ist auch dann praktisch, wenn die Attribute eines Konzepts Verweise auf die Namen anderer Konzepte oder deren Attribute sind oder wenn die Quelldaten nicht nur Fakten, sondern auch deren Struktur enthalten. Beispielsweise können die Quelldaten eine Beschreibung der Schemata von XML-Dokumenten oder -Tabellen in einer Datenbank enthalten. Die Originaldaten können auch zusätzliche Informationen zu den Fakten enthalten, z. B. Datentypen, Formate oder Standardwerte, Validierungsbedingungen oder einige Regeln. Außerdem können die Anfangsdaten ein Modell von etwas beschreiben, und die Modellierungskomponente ist für die Erstellung eines Metamodells verantwortlich. Bei der Arbeit mit Texten in natürlicher Sprache wird außerdem davon ausgegangen, dass die Quelldaten nicht nur Aussagen, sondern auch Aussagen zu Aussagen enthalten.In all diesen Fällen reicht die Logik erster Ordnung nicht aus und es wird eine ausdrucksstärkere Sprache benötigt.



Betrachten Sie als einfaches Beispiel den Fall, in dem die Daten einige Objekte enthalten, sowie die Regeln zum Überprüfen der Attribute dieser Objekte als separate Entität:



fact validationRule {objectName: “someObject”, attributeName: “someAttribute”, rule: function(value) {...}}
      
      





Validierungsergebnisse können durch das folgende Konzept beschrieben werden:



concept validationRuleCheck (
	objectName = r.objectName,
	attributeName = r.attrName,
	result = r.rule(o.<r.attrName>)
) from validationRule r, <r.objectName> o 
where defined(o.<r.attrName>)
      
      





Logik höherer Ordnung eröffnet einige ziemlich interessante Möglichkeiten für verallgemeinerte und Metaprogrammierung. Wir konnten nur die allgemeine Idee berücksichtigen. Dieser Bereich ist recht komplex und erfordert in Zukunft gründliche Forschung. Sowohl unter dem Gesichtspunkt der Wahl eines bequemen Designs der Sprache als auch unter dem Gesichtspunkt ihrer Leistung.



Schlussfolgerungen



Während der Arbeit an der Modellierungskomponente musste ich mich mit ziemlich spezifischen Fragen der logischen Programmierung befassen, wie der Arbeit mit booleschen Variablen, der Semantik des Negationsoperators und Elementen der Logik höherer Ordnung. Wenn sich herausstellt, dass mit Variablen alles recht einfach ist, gibt es keinen einheitlichen Ansatz für die Implementierung des Negationsoperators und der Logik höherer Ordnung, und es gibt mehrere Lösungen, die ihre eigenen Eigenschaften, Vor- und Nachteile haben.



Ich habe versucht, eine Lösung zu wählen, die in der Praxis leicht zu verstehen und bequem ist. Ich zog es vor, den monolithischen Operator der Negation als Ablehnung in drei separate Operatoren der Booleschen Negation aufzuteilen, um die Ableitbarkeit eines Konzepts und die Existenz eines Attributs in einem Objekt zu überprüfen. Bei Bedarf können diese drei Operatoren kombiniert werden, um die für den jeweiligen Fall erforderliche Negationssemantik zu erhalten. Für die Regel der Inferenznegation habe ich mich für die Standard-SLDNF-Auflösung entschieden. Obwohl es nicht in der Lage ist, mit inkonsistenten rekursiven Anweisungen umzugehen, ist es viel einfacher zu verstehen und zu implementieren als komplexere Alternativen wie die persistente Modellsemantik oder die begründete Semantik.



Für die Generalisierung und Metaprogrammierung wird eine Logik höherer Ordnung benötigt. Mit generischer Programmierung meine ich die Fähigkeit, neue Konzepte aus einer Vielzahl von untergeordneten Konzepten zu erstellen, ohne an bestimmte Namen der letzteren gebunden zu sein. Mit Metaprogrammierung meine ich die Fähigkeit, die Struktur einiger Konzepte mit Hilfe anderer Konzepte zu beschreiben. Ich entschied mich, die HiLog-Sprache als Modell für die übergeordneten Logikelemente der Modellierungskomponente zu verwenden. Es verfügt über eine flexible und bequeme Syntax, mit der Sie Variablen an den Positionen von Prädikatnamen sowie eine einfache und klare Semantik verwenden können.



Ich plane, den nächsten Artikel dem Ausleihen aus der SQL-Welt zu widmen: verschachtelte Abfragen und Aggregationen. Ich werde auch über eine andere Art von Konzepten sprechen, die keine Inferenz verwenden, sondern Entitäten direkt mit einer bestimmten Funktion generiert werden. Und wie Sie damit Tabellen, Arrays und assoziative Arrays in das Objektformat konvertieren und in den logischen Inferenzprozess einbeziehen können (in Analogie zur SQL UNNEST-Operation, die Arrays in das Tabellenformat konvertiert).



Der vollständige Text im wissenschaftlichen Stil in englischer Sprache ist hier verfügbar .



Links zu früheren Veröffentlichungen:



Entwerfen einer Programmiersprache mit mehreren Paradigmen. Teil 1 - Wofür ist es?

Wir entwerfen eine Multi-Paradigma-Programmiersprache. Teil 2 - Vergleich von Gebäudemodellen in PL / SQL, LINQ und GraphQL Wir

entwerfen eine Programmiersprache mit mehreren Paradigmen. Teil 3 - Überblick über Wissensrepräsentationssprachen Wir

entwerfen eine Programmiersprache mit mehreren Paradigmen. Teil 4 - Grundkonstruktionen der Modellierungssprache



All Articles