Abstrakte Wikipedia/Funktionsmodell

From Meta, a Wikimedia project coordination wiki
Jump to navigation Jump to search
This page is a translated version of the page Abstract Wikipedia/Function model and the translation is 100% complete.
Other languages:
Bahasa Indonesia • ‎Deutsch • ‎English • ‎Scots • ‎dansk • ‎français • ‎polski • ‎português do Brasil • ‎suomi • ‎українська • ‎日本語 • ‎한국어

Wikifunctions ist ein mehrsprachiger Katalog von Funktionen, zu dem jeder beitragen und wo jeder Funktionen erstellen, pflegen, aufrufen und benutzen kann. Jede Funktion kann mehrere Implementierungen haben, z. B. in verschiedenen Programmiersprachen oder mit unterschiedlichen Algorithmen. Es ist eine “Wikipedia von Funktionen” und ein von der Wikimedia-Stiftung betriebenes Schwesterprojekt.

Dieses Dokument deckt das Datenmodell und das Auswertungsmodell von Wikifunctions ab.

In dieser Modellbeschreibung beziehen sich großgeschriebene Begriffe auf Begriffe, die im Glossar definiert sind.
Die Lektüre des walkthrough of a previous prototype ist sehr hilfreich, um ein besseres Gespür dafür zu bekommen, was hier vor sich geht, bevor du das folgende Modell liest.

Z1/ZObjekte

Wikifunctions ist ein Wiki. Wie in allen Wikis wird der Inhalt von Wikifunctions vorwiegend in Wiki-Seiten gespeichert. Wiki-Seiten können einzeln bearbeitet werden, dennoch muss das Projekt als Ganzes eine gewisse Konsistenz bewahren. Ebenso sollte man Wiki-Seiten einzeln bearbeiten können, ohne die ganzen anderen Seiten verstehen zu müssen.

Wir führen ZObjects zur Darstellung des Inhalts von Wikifunctions ein. Jede Wiki-Seite des Hauptnamensraums von Wikifunctions enthält genau ein ZObjekt des Typs Z2/Persistent object. Andere Namensräume können anderen Inhalt haben wie zum Beispiel Richtlinienseiten, Nutzerseiten, Diskussionsseiten etc. Ein ZObject kann als JSON-Objekt serialisiert werden.

Ein ZObject besteht aus einer Liste von Schlüssel/Wert-Paaren.

  • Jeder Wert in einem Schlüssel/Wert-Paar ist ein ZObject.
  • Jeder Schlüssel kann in jedem ZObjekt nur einmal vorkommen (aber er kann in einem eingebetteten ZObjekt erneut erscheinen).

ZObjekte sind im Grunde abstrakte Syntax-Bäume. Gäbe es ein ZLNG ("Zu Lang; Nicht Gelesen") des Projekts, wäre es wahrscheinlich "so was wie LISP in JSON”. Ziel ist es, eine einfache UX zur Erstellung und Manipulation von ZObjekten über eine Wiki-Schnittstelle zur Verfügung zu stellen, und so eine Kodierumgebung zu schaffen, die eine große Anzahl von Mitwirkenden erreichen und zu einem Wikimedia-Projekt mit einer aktiven Community werden kann.

Jedes ZObjekt muss einen Schlüssel vom Z1K1/Type haben mit einem Wert, der zu einem Z4/Typ auswertet (dies wird gleich weiter unten erklärt).

Wir verwenden die Notation ZID/label, um ZIDs mehr oder weniger lesbar zu bezeichnen, wobei ‘ZID’ eine ZObjekt-ID oder ein Schlüssel zu einem solchen Objekt und ‘label’ das (englisch-sprachige) Label ist, das dieser sprachneutralen ID oder diesem Schlüssel zugeordnet ist.

Syntax

Die kanonische Darstellung eines ZObjekts ist eine Teilmenge von JSON. Ein formgerechtes ZObjekt hat folgende Syntax:

ZObject := String | List | Record
String  := "Character*" // inbesondere wie in JSON / ECMA-404
List  := [ ( ZObject ( , ZObject )* )? ]
Record  := { "Z1K1": ZObject ( , "Key": ZObject )* }
Key  := ZNumber KNumber | KNumber
ZNumber := Z Number
KNumber := K Number
Number  := [1-9][0-9]* // eine dezimale Ganzzahl größer Null

wobei:

  • fett-Zeichen terminale Symbole sind;
  • kursiv-Zeichen für nicht-terminale Symbole benutzt werden;
  • ( ) werden benutzt, um eine Gruppe einzuklammern, die eine oder mehrere Alternativen enthalten kann, die mittels | getrennt werden;
  • eine Gruppe kann mittels * 0..n-mal oder mit + 1..n-mal wiederholt werden.
  • eine Gruppe, an die sich ein ? anschließt, ist optional;
  • Leerzeichen können wie in JSON verwendet werden.

Daraus ergibt sich die Untermenge von JSON ohne Zahlen, Null, Boolesche Werte und mit nur wenigen Schlüsseln.

Um formgerecht zu sein, muss der Z1K1-Schlüssel einen Wert haben, der zu einem Z4/Typ ausgewertet werden kann, d.h. er ist entweder ein formgerechtes Literal des Typs Z4, eine Referenz oder ein Funktionsaufruf.

Serialisierung

Ein ZObject wird in seine kanonische JSON-Darstellung serialisiert. Verwendet werden dabei als Schlüssel die abstrakten ZID-Schlüssel ("Z1K1" usw.) und als Werte entweder:

  • die kanonische JSON-Darstellung eines anderen (transienten) ZObjekts,
  • die ZID eines persistenten ZObjekts (d.h. eines String-Werts mit einem führenden Großbuchstaben des lateinischen Alphabets und anschließenden Ziffern),
  • einem einfachen String-Wert, der alternativ als ein ZObjekt (Z6/string type) selbst dargestellt werden kann, oder
  • ein JSON-Array als Darstellung für Z10/list objects.

Eine alternative, besser lesbare Darstellung kann gegeben werden, indem die abstrakten Schlüssel und ZIDs durch ihre Label in einer bestimmten Sprache ersetzt werden, die "labelisierte" Darstellung.

Die folgende Tabelle zeigt ein Beispiel für ein ZObject, das die positive Ganzzahl 2 darstellt. Links sehen wir das englisch- und in der Mitte das deutsch-labelisierte ZObject, und rechts sehen wir das ZObject mit Nutzung von ZIDs.

{
  "type": "positive integer",
  "base 10 representation": "2"
}
{
  "Typ": "natürliche Zahl",
  "Dezimaldarstellung": "2"
}
{
  "Z1K1": "Z10070",
  "Z10070K1": "2"
}

Wie man sieht, müssen die Label nicht auf englisch sein, sie können vielmehr in jeder der mehr als 300 Sprachen sein, die Wikifunctions unterstützt.

Man beachte auch: wenn der Schlüssel ein Z2K1/id- oder Z6K1/string-Wert ist, dann wird der Wert immer als einfacher String-Wert serialisiert.

Deserialisierung

Die kanonische JSON-Darstellung eines ZObjekts wird im Wesentlichen durch Standard-JSON-Parsing in sein rechenbares Äquivalent umgewandelt - das heißt, das JSON-Objekt wird zu einem ZObjekt mit denselben (abstrakten) Schlüsseln. Die vier verschiedenen zulässigen Werteformate werden wie folgt geparst:

Wenn der Wert ein anderes JSON-Objekt ist, sollte dieses wiederum rekursiv denselben Deserialisierungsregeln folgen.

Wenn es sich bei einem String-Wert um einen einfachen String handelt, der mit einem Großbuchstaben des lateinischen Alphabets beginnt, auf den eine Zahl folgt, wird er als Verweis auf ein persistentes ZObjekt, eine Z9/Referenz, interpretiert. Im obigen Beispiel wird der Wert von Z1K1/type als Zeichenkette "Z10070" angegeben, sollte aber als Referenz - ZObjekt geparst werden, das selbst wie folgt dargestellt werden würde:

{
  "type": "reference",
  "reference id": "positive integer"
}
{
  "Z1K1": "Z9",
  "Z9K1": "Z10070"
}

Ist der Wert hingegen eine einfache Zeichenkette, die nicht mit einem Großbuchstaben des lateinischen Alphabets gefolgt von einer Zahl beginnt, wird die Zeichenkette als ZObject vom Typ Z6/String und dem angegebenen Wert verstanden. D.h. der Wert der Z10070K1/Dezimal-Darstellung wird als String "2" angegeben, sollte aber als ZObjekt geparst werden, das wie folgt aussieht:

{
  "type": "string",
  "string value": "2"
}
{
  "Z1K1": "Z6",
  "Z6K1": "2"
}

Man beachte, dass der Deserialisierungsprozess nicht wiederholt werden sollte, wie es in diesen Fällen möglich wäre (der Wert des Z1K1/Type key würde als vom Typ Z9/Reference interpretiert werden, und der Wert von Z6K1/string wiederum als vom Typ Z6/string), da dies zu einer unendlichen Rekursion führen würde; es handelt sich an dieser Stelle um intern repräsentierte ZObjekte, nicht um serialisiertes JSON.

Wenn wir angesichts dieser Deserialisierungsregeln eine Zeichenkette schreiben müssen, die mit einem Großbuchstaben des lateinischen Alphabets beginnt und von einer Zahl gefolgt wird, müssen wir diese entschlüsseln. Hier wird die Ausnahme bezüglich Z6K1/string value oben verwendet. Hier liegt der String-Wert "Z1" auf dem Schlüssel Z11K2/text.

{
  "type": "text",
  "language": "English",
  "text": {
    "type": "string",
    "string value": "Z1"
  }
}
{
  "Z1K1": "Z11",
  "Z11K1": "Z1251",
  "Z11K2": {
    "Z1K1": "Z6",
    "Z6K1": "Z1"
  }
}

Abschließend ist zu beachten, dass JSON-Arrays als Z10/List ZObjects deserialisiert werden. Die vollständige Interpretation von JSON-Arrays wird im Abschnitt über Z10/Listen weiter unten erläutert.

Normalisierung

Für die Verarbeitung von ZObjekten durch die Auswertefunktion werden alle ZObjekte in eine normalisierte Version ihrer selbst umgewandelt. Die normalisierte Version ähnelt der deserialisierten Version, aber wir verlassen uns nicht auf irgendeine Implizität, ob ein String-Wert als Z6/String oder als Z9/Reference zu interpretieren ist, sondern sie werden alle als explizite ZObjekte ausgedrückt.

Das heißt, die normalisierte Darstellung eines ZObjekts ist ein Baum, bei dem alle Blätter entweder vom Typ Z6/String oder Z9/Reference sind.

Das bedeutet auch, dass alle Z10/Listen als ZObjekte und nicht als Array dargestellt werden. Außerdem muss jedes Z10/List-Objekt die beiden Schlüssel (Z10)K1/Kopf und (Z10)K2/Schwanz haben. Eine leere Liste (einschließlich des Schwanzes einer Liste mit einem Element) ist ein Z13/Leer.

Die folgende normalisierte Form stellt die positive Ganzzahl 2 dar.

{
  "type": {
    "type": "reference",
    "reference id": "positive integer"
   },
   "base 10 representation": {
     "type": "string",
     "string value": "2"
   }
}
{
  "Z1K1": {
    "Z1K1": "Z9",
    "Z9K1": "Z10070"
   },
   "Z10070K1": {
     "Z1K1": "Z6",
     "Z6K1": "2"
   }
}

Normalisierte Ansichten werden nur als Eingaben für die Auswerte-Engine verwendet. Sie stellen sicher, dass die Eingaben für die Auswertung immer einheitlich und einfach zu verarbeiten sind und nur eine minimale Anzahl von Sonderfällen erfordern.

Persistent und transient

Jedes ZObjekt der obersten Ebene, das in einer Wikifunctions-Wikiseite gespeichert ist, ist ein Z2/Persistentes Objekt. ZObjekte, die nicht auf einer eigenen Wikiseite gespeichert sind, werden als transiente ZObjekte bezeichnet.

Jedes persistente ZObjekt muss eine Z2K1/id haben, eine ZID, die dem Namen der Wikiseite entspricht, auf der es gespeichert ist. Nehmen wir an, dass es ein ZObjekt für die positive Ganzzahl 2 gibt, die wir zuvor gesehen haben, und dass es auf der Seite Z382/two gespeichert ist. So könnte es aussehen (man beachte, dass die ZIDs nicht unbedingt diejenigen sind, die wir in Wikifunctions verwenden werden).

{
  "type": "persistent object",
  "id": "Z382",
  "value": {
    "type": "positive integer",
    "base 10 representation": "2"
  },
  "label": {
    "type": "multilingual text",
    "texts": [
      {
        "type": "text",
        "language": "English",
        "text": "two"
      },
      {
        "type": "text",
        "language": "German",
        "text": "zwei"
      }
    ]
  }
}
{
  "Z1K1": "Z2",
  "Z2K1": "Z382",
  "Z2K2": {
    "Z1K1": "Z10070",
    "Z10070K1": "2"
  },
  "Z2K3": {
    "Z1K1": "Z12",
    "Z12K1": [
      {
        "Z1K1": "Z11",
        "Z11K1": "Z1251",
        "Z11K2": "two"
      },
      {
        "Z1K1": "Z11",
        "Z11K1": "Z1254",
        "Z11K2": "zwei"
      }
    ]
  }
}

Das Z2/Persistent object stellt Metadaten für das ZObjekt zur Verfügung, das in den Z2K2/value eingebettet ist.

Das Z2K3/label ist ein ZObjekt vom Typ Z12/multilingual text, das einen Z3/Key und Z12K1/texts hat, die auf eine Z10/Liste von Z11/monolingual text-ZObjekten zeigen (man denke daran, dass eine Z10/list in der JSON-Darstellung als Array dargestellt wird). Das Label ermöglicht die Labelisierung.

Es gibt weitere Z3/Keys auf Z2/persistent object, die Metadaten zur Dokumentation, zur Darstellung im Wiki und zur besseren Auffindbarkeit bereitstellen. Sie sind alle auf Z2/persistent object definiert.

Z9/Referenzen

Wie wir an der positiven Zahl 2 gesehen haben, ist der Wert von Z1K1/type ein ZObjekt vom Typ Z9/Reference. Wenn wir das erweitern, würde es wie folgt aussehen.

{
  "type": {
    "type": "reference",
    "reference id": "positive integer"
  },
  "base 10 representation": "2"
}
{
  "Z1K1": {
    "Z1K1": "Z9",
    "Z9K1": "Z10070"
  },
  "Z10070K1": "2"
}

Eine Z9/Reference ist eine Referenz auf den Z2K2/value des ZObjekts mit der angegebenen ID und bedeutet, dass dieser Z2K2/value hier eingefügt werden soll. Um ein Beispiel zu nennen, sei folgendes Array mit einem einzelnen Element gegeben:

[
  "two"
]
[
  "Z382"
]

Das Element ist eine Z9/Reference-Kurzform, die in der Langform so aussehen würde (wie im Abschnitt über Deserialisierung erläutert):

[
  {
    "type": "reference",
    "reference id": "two"
  }
]
[
  {
    "Z1K1": "Z9",
    "Z9K1": "Z382"
  }
]

Und da es sich um eine Referenz handelt, ist diese durch den Z2K2/value des Z2/Persistent object mit der ZID Z382 (wie oben angegeben) zu ersetzen, d. h. es würde wie folgt aussehen:

[
  {
    "type": "positive integer",
    "base 10 representation": "2"
  }
]
[
  {
    "Z1K1": "Z10070",
    "Z10070K1": "2"
  }
]

Man beachte, dass wenn eine Z8/Function einen Argumenttyp Z2/Persistent object hat, dann wird anstelle des Z2K2/Wertes das Z2/Persistent object selbst eingesetzt.

Z4/Typen

Typen sind ZObjekte vom Typ Z4/Type. ZObjekte eines Typs werden als Instanzen dieses Typs bezeichnet. So war Z382/two, die wir oben gesehen haben, eine Instanz des Typs Z10070/positive integer.

Ein Typ bietet uns die Möglichkeit, die Gültigkeit eines ZObjekts dieses Typs zu überprüfen. Ein Typ deklariert normalerweise die für seine Instanzen verfügbaren Schlüssel und eine Funktion, die zur Validierung der Instanzen verwendet wird.

Hier ist (ein vereinfachter) Typ für positive Ganzzahlen.

{
  "type": "persistent object",
  "id": "Z10070",
  "value": {
    "type": "type",
    "identity": "positive integer",
    "keys": [
      {
        "type": "key",
        "value type": "string",
        "key id": "Z10070K1",
        "label": {
          "type": "multilingual text",
          "texts": [
            {
              "type": "text",
              "language": "English",
              "text": "base 10 representation"
            },
            {
              "type": "text",
              "language": "German",
              "text": "Dezimaldarstellung"
            }
          ]
        },
        "default": "value required"
      }
    ],
    "validator": "validate positive integer"
  },
  "label": {
    "type": "multilingual text",
    "texts": [
      {
        "type": "text",
        "language": "English",
        "text": "positive integer"
      },
      {
        "type": "text",
        "language": "German",
        "text": "natürliche Zahl"
      }
    ]
  }
}
{
  "Z1K1": "Z2",
  "Z2K1": "Z10070",
  "Z2K2": {
    "Z1K1": "Z4",
    "Z4K1": "Z10070",
    "Z4K2": [
      {
        "Z1K1": "Z3",
        "Z3K1": "Z6",
        "Z3K2": "Z10070K1",
        "Z3K3": {
          "Z1K1": "Z12",
          "Z12K1": [
            {
              "Z1K1": "Z11",
              "Z11K1": "Z1251",
              "Z11K2": "base 10 representation"
            },
            {
              "Z1K1": "Z11",
              "Z11K1": "Z1254",
              "Z11K2": "Dezimaldarstellung"
            }
          ]
        },
        "Z3K4": "Z25"
      }
    ],
    "Z4K3": "Z10559"
  },
  "Z2K3": {
    "Z1K1": "Z12",
    "Z12K1": [
      {
        "Z1K1": "Z11",
        "Z11K1": "Z1251",
        "Z11K2": "positive integer"
      },
      {
        "Z1K1": "Z11",
        "Z11K1": "Z1254",
        "Z11K2": "natürliche Zahl"
      }
    ]
  }
}

Um den Kern des Typs besser sichtbar zu machen, betrachten wir nur den Z4/Type und entfernen die Label:

{
  "type": "type",
  "identity": "positive integer",
  "keys": [
    {
      "type": "key",
      "value type": "string",
      "keyid": "Z10070K1",
      "default": "value required"
    }
  ],
  "validator": "validate positive integer"
}
{
  "Z1K1": "Z4",
  "Z4K1": "Z10070",
  "Z4K2": [
    {
      "Z1K1": "Z3",
      "Z3K1": "Z6",
      "Z3K2": "Z10070K1",
      "Z3K4": "Z25"
    }
  ],
  "Z4K3": "Z10559"
}

Der Typ Z10070/positive integer definiert in Z4K2/keys die neue Z3/Key-Z10070K1/Dezimal-Darstellung, die wir oben in der Instanz für die Zahl 2 verwendet hatten.

Z4K3/validator zeigt auf eine Z8/Funktion, die eine Instanz als Argument nimmt und eine Z10/Liste von Z5/Fehlern zurückgibt. If the Z10/List is empty then the instance has passed the validation. In the given case, the Z8/Function would do the following checks:Wenn die Z10/Liste leer ist, hat die Instanz die Validierung bestanden. Im gegebenen Fall würde die Z8/Funktion die folgenden Prüfungen durchführen:

  • Es gibt neben den Metadaten auf der Instanz nur einen einzigen Schlüssel, die Z10070K1/Dezimal-Darstellung.
  • Der Wert der Dezimaldarstellung hat den Typ Z6/String.
  • Die Dezimaldarstellung enthält nur Ziffern.
  • Die Dezimaldarstellung beginnt nicht mit einer 0, es sei denn, sie hat eine Länge von 1.
  • Die Dezimaldarstellung liegt unter 232 (oder was auch immer die Grenze für diesen Typ ist).

Man beachte, dass alle diese Überprüfungen durch Z8/Funktionen durchgeführt werden, die von Mitwirkenden bereitgestellt werden, und dass alle Typen von Mitwirkenden definiert und geändert werden können. Es ist nichts in Stein gemeißelt bezüglich des Zahlentyps, den wir hier verwenden.

Eine Instanz kann Schlüssel verwenden, die nicht für den Typ definiert sind. Es liegt an der Validierungsfunktion, dies zuzulassen oder nicht. Zum Beispiel verwenden Instanzen von Z7/Function call oft Schlüsssel, die nicht beiZ7/Function call definiert sind, wie im Abschnitt über Z7/Funktionsaufrufe zu sehen ist. Bei den meisten Validierungsfunktionen wird jedoch erwartet, dass alle Schlüssel definiert sind.

Aber ein paar Dinge sind definitiv festgelegt, wie z. B. das Verhalten von Z7/Funktionsaufruf. Mehr dazu später.

Z3/Schlüssel

Bei den meisten Validierungsfunktionen wird jedoch erwartet, dass alle Schlüssel definiert sind. Wenn ihnen eine ZID vorangestellt ist, werden sie als Globale Schlüssel und andernfalls als Lokale Schlüssel bezeichnet. Zum Beispiel sind die folgenden zwei Darstellungen gleichwertig.

{
  "Z1K1": "Z7",
  "Z7K1": "Z144",
  "Z144K1": "Z382",
  "Z144K2": "Z382"
}
{
  "Z1K1": "Z7",
  "Z7K1": "Z144",
  "K1": "Z382",
  "K2": "Z382"
}

Globale Schlüssel sind Namensargumente, während lokale Schlüssel Positionsargumente sind.

  • Als Faustregel gilt, dass man, wann immer möglich, globale Schlüssel verwenden sollte.
  • Der Hauptanwendungsfall für lokale Schlüssel ist, wenn eine Z8/Funktion oder ein Z4/Typ ad hoc erstellt wird und daher keine globalen Schlüssel haben kann, da die erstellte Z8/Funktion oder der Z4/Typ selbst nicht persistent ist.

Ein Beispiel ist der Aufruf von Z79/curry rechts im Abschnitt über Z20/Tests weiter unten.

Ein globaler Schlüssel ist immer für das ZObjekt definiert, auf das sich der ZID-Teil seiner ID bezieht.

Z8/Funktionen

In der Definition von Z10070/positive Ganzzahl haben wir einen ersten Hinweis auf eine Z8/Funktion gesehen, Z10559/validate positive integer. Hier werden wir eine viel einfachere Funktion verwenden, Z144/add. Z144/add ist eine Z8/Funktion, die zwei Z10070/positive Ganzzahlen annimmt und eine Z10070/positive Ganzzahl zurückgibt.

Man beachte, dass wir den Wert von Z1K1/Type vorerst leer lassen. Wir kommen darauf in einem späteren Abschnitt zurück. Wir zeigen nur den Wert an.

{
 "type": { ... },
 "arguments": [
   {
     "type": "argument declaration",
     "argument type": "positive integer",
     "key id": "Z144K1",
     "label": { ... }
   },
   {
     "type": "argument declaration",
     "argument type": "positive integer",
     "key id": "Z144K2",
     "label": { ... }
   }
 ],
 "return type": "positive integer",
 "tests": [
   "add one and zero",
   "add two and two"
 ],
 "identity": "Z144"
}
{
 "Z1K1": { ... },
 "K1": [
   {
     "Z1K1": "Z17",
     "Z17K1": "Z10070",
     "Z17K2": "Z144K1",
     "Z17K3": { ... }
   },
   {
     "Z1K1": "Z17",
     "Z17K1": "Z10070",
     "Z17K2": "Z144K2",
     "Z17K3": { ... }
   }
 ],
 "K2": "Z10070",
 "K3": [
   "Z1441",
   "Z1442"
 ],
 "K5": "Z144"
}

Um übersichtlich zu bleiben, haben wir die Z17K3/Labels aus den Z17/Argument-Deklarationen entfernt, die über Z17K2/Schlüssel-IDs identifiziert werden. Aber genau wie die Z3/Keys auf Z4/Types haben sie Labels in allen unterstützten Sprachen. Die Schlüssel sind global, wenn die Z8/Funktion persistent, und lokal, wenn sie transient ist.

Die Funktion wird durch die (weggelassene) Dokumentation spezifiziert, aber auch durch die K3/Tests und die K1/Typ-Deklarationen zu den Argumenten und zum K2/Wiedergabetyp. Da darüber hinaus eine Funktion mehrere Z14/Implementierungen haben kann (siehe unten), bestätigen sich die Implementierungen gegenseitig.

Z8/Funktionen dürfen keine zustandsändernden Nebeneffekte haben.

Z7/Funktionsaufrufe

Das folgende ZObjekt stellt einen Funktionsaufruf dar. In der zweiten Zeile sehen wir eine kompaktere Darstellung des Funktionsaufrufs, die eine Syntax verwendet, die für Funktionsaufrufe vertrauter ist.

{
  "type": "function call",
  "function": "add",
  "left": "two",
  "right": "two"
}
{
  "Z1K1": "Z7",
  "Z7K1": "Z144",
  "Z144K1": "Z382",
  "Z144K2": "Z382"
}
add(two, two) Z144(Z382, Z382)

Wenn man Literale anstelle von persistenten ZObjekten für die Argumente verwendet, würde dies wie folgt aussehen.

  • Beachte, dass wir die Literale mit dem Z10070/positive integer als Konstruktor erstellen.
  • Alle Z4/Types können auf diese Weise aufgerufen werden, wobei für jeden ihrer Schlüssel ein Wert bereitgestellt wird.
  • Dies ist kein Z7/Funktionsaufruf, sondern eine Notation für das Objekt des betreffenden Z4/Typs.
{
  "type": "function call",
  "function": "add",
  "left": {
    "type": "positive integer",
    "base 10 representation": "2"
  },
  "right": {
    "type": "positive integer",
    "base 10 representation": "2"
  }
}
{
  "Z1K1": "Z7",
  "Z7K1": "Z144",
  "Z144K1": {
    "Z1K1": "Z10070",
    "Z10070K1": "2"
  },
  "Z144K2": {
    "Z1K1": "Z10070",
    "Z10070K1": "2"
  }
}
add(positive_integer("2"), positive_integer("2")) Z144(Z10070("2"), Z10070("2"))

When this Z7/Function call gets evaluated, it results as expected in the number four.

{
  "type": "positive integer",
  "base 10 representation": "4"
}
{
  "Z1K1": "Z10070",
  "Z10070K1": "4"
}
positive_integer("4") Z10070("4")

Die Auswertung wird wiederholt auf das Auswerteergebnis angewendet, bis ein Fixpunkt erreicht ist.

Eine Z8/Funktion hat einen optionalen K4/implementation key, der auf die zu verwendende Z14/Implementation zeigt. Dies wird normalerweise von der Evaluierungs-Engine als Teil des Evaluierungsschritts ausgefüllt, wenn aber eine spezifische Implementierung angegeben wird, wird die Auswertefunktion diese berücksichtigen.

Z14/Implementierungen

Jede Z8/Funktion kann eine Anzahl verschiedener Z14/Implementierungen haben. Es gibt drei Haupttypen von Z14/Implementierungen: Built-Ins, Z16/Code oder durch Komposition anderer Z8/Funktionen.

Nehmen wir die Z144/add-Funktion und betrachten wir fünf verschiedene Z14/Implementierungen.

Built-In-Implementierungen

Eine Builtin-Implementierung teilt der Laufzeit-Auswertefunktion mit, dass sie ein entsprechendes Auswertungsergebnis zurückgeben soll. Built-Ins sind der Auswertefunktion hart kodiert. Z14K4/builtin bezieht sich auf die hartkodierte Built-In-ID (die normalerweise die ZID des Z2/Persistent-Objekts sein sollte, aber nicht sein muss).

{
  "type": "implementation",
  "implements": "add",
  "builtin": "Z1443"
}
{
  "Z1K1": "Z14",
  "Z14K1": "Z144",
  "Z14K4": "Z1443"
}
// implementation(add, , , Z1443)

Man braucht Built-Ins nicht als ZObjekte zu deklarieren, um sie verwenden zu können. Eine Auswertefunktion würde alle ihre eigenen Built-Ins kennen und könnte sie nach Belieben verwenden. Es ist immer noch sinnvoll, ein Built-In zu deklarieren, um es direkt aufrufen zu können.

Z16/Code

Beachte: Dieser Abschnitt müsste überarbeitet werden, um für die unterschiedlichen Programmiersprachen unterschiedliche Implementierungen vorzusehen.

Eine Implementierung in Z16/Code stellt einen Code-Schnipsel in einer bestimmten Programmiersprache dar.

{
  "type": "implementation",
  "implements": "add",
  "code": {
    "type": "code",
    "language": "javascript",
    "source": "K0 = Z144K1 + Z144K2"
  }
}
{
  "Z1K1": "Z14",
  "Z14K1": "Z144",
  "Z14K3": {
    "Z1K1": "Z16",
    "Z16K1": "Z100701",
    "Z16K2": "K0 = Z144K1 + Z144K2"
  }
}
// implementation(add, , code(javascript, "_0 = _1 + _2"))
{
  "type": "implementation",
  "implements": "add",
  "code": {
    "type": "code",
    "language": "python3",
    "source": "K0 = Z144K1 + Z144K2"
  }
}
{
  "Z1K1": "Z14",
  "Z14K1": "Z144",
  "Z14K3": {
    "Z1K1": "Z16",
    "Z16K1": "Z100703",
    "Z16K2": "K0 = Z144K1 + Z144K2"
  }
}
// implementation(add, , code(python3, "_0 = _1 + _2"))

In diesem Fall sind die Implementierungen für JavaScript und Python identisch, aber dieser Fall tritt offensichtlich selten ein.

Die Auswertefunktion würde wissen, wie die gegebenen ZObjekte, die die Argumente repräsentieren, in die unterstützten Programmiersprachen umzuwandeln sind (was auch der Grund dafür ist, dass wir den positiven Integer-Typ auf 32 Bit beschränken - da das Verhalten für die beiden unterstützten Sprachen in diesem Bereich äquivalent ist), wie der bereitgestellte Codeschnipsel auszuführen ist und wie das Ergebnis dann wieder in ein ZObjekt umgewandelt werden kann, das das Ergebnis repräsentiert.

Schließlich würde die Übersetzung von ZObjekten in die nativen Werte der unterstützten Programmiersprachen innerhalb von Wikifunctions selbst gehandhabt werden (was ein neues Design-Dokument erfordern wird). Bis dahin unterstützen wir Z16/Code nur für Argumente und Rückgabetypen, die von der Auswertefunktion hartkodiert unterstützt werden.

Komposition

Die portabelste (aber oft auch die langsamste) Z14-Implementierung wird durch Komposition anderer Z8-Funktionen erreicht. Wir betrachten zwei verschiedene Implementierungen.

Wir zeigen sowohl das ZObjekt der Implementierung als auch eine einfacher zu lesende Notation, die auf der Funktionsaufrufsyntax basiert (Beachte, dass diese Syntax auch Leerzeichen in Bezeichnungen durch Unterstriche ersetzt).

{
 "type": "implementation",
 "implements": "add",
 "composition": {
   "type": "function call",
   "function": "lambda to integer",
   "arg": {
     "type": "function call",
     "function": "lambda add",
     "left": {
       "type": "function call",
       "function": "integer to lambda",
       "arg": {
         "type": "argument reference",
         "reference": "left"
       }
     },
     "right": {
       "type": "function call",
       "function": "integer to lambda",
       "arg": {
         "type": "argument reference",
         "reference": "right"
       }
     }
   }
 }
}
{
 "Z1K1": "Z14",
 "Z14K1": "Z144",
 "Z14K2": {
   "Z1K1": "Z7",
   "Z7K1": "Z71",
   "Z71K1": {
     "Z1K1": "Z7",
     "Z7K1": "Z77",
     "Z77K1": {
       "Z1K1": "Z7",
       "Z7K1": "Z72",
       "Z72K1": {
         "Z1K1": "Z18",
         "Z18K1": "Z144K1"
       }
     },
     "Z77K2": {
       "Z1K1": "Z7",
       "Z7K1": "Z72",
       "Z72K1": {
         "Z1K1": "Z18",
         "Z18K1": "Z144K2"
       }
     }
   }
 }
}
lambda_to_positive_integer(

  lambda_add(
    positive_integer_to_lambda(left),
    positive_integer_to_lambda(right)
  )
)

Z71(

  Z77(
    Z72(Z144K1),
    Z72(Z144K2)
  )
)

Wikifunctions enthält eine vollständige Implementierung des Lambda-Kalküls (das ist ja auch eine der Anregungen für den Namen). Diese Komposition übersetzt die Z10070/positiven Ganzzahlen in Church-Zahlen mit Z72/positive Ganzzahl nach Lambda, verwendet dann die Addition, wie sie im Lambda-Kalkül von Wikifunctions implementiert ist (Z77/lambda add), und das Ergebnis wird von einer Church-Zahl mit Z71/lambda nach positive Ganzzahl in eine Z10070/positive Ganzzahl übersetzt. (solltest du neugierig sein: Z77/lambda add ist auf die gleiche Weise implementiert wie Z1445/add rekursiv unten, aber unter Verwendung von Lambda-basierten Funktionen, wie sie über Church-Zahlen definiert sind).

Dies verlässt sich vollständig auf das Lambda-Kalkül. Das ist alles andere als schnell - aber es erlaubt uns, einen gut verstandenen Formalismus und eine sehr einfache Implementierung davon zu verwenden, um sicherzustellen, dass die anderen Implementierungen von Z144/add korrekt sind - zugegebenermaßen ist dies wahrscheinlich von geringerem Interesse für die Addition, aber wir können uns vorstellen, dass es Z8/Funktionen gibt, die offensichtlich korrektere und viel schlauere und schnellere Implementierungen haben. Wikifunctions kann diese Implementierungen gegeneinander kreuztesten und uns so ein gewisses Gefühl der Sicherheit in Bezug auf ihre Richtigkeit geben.

{
 "type": "implementation",
 "implements": "add",
 "implementation": {
   "type": "function call",
   "function": "if",
   "condition": {
     "type": "function call",
     "function": "is zero",
     "arg": {
       "type": "argument reference",
       "reference": "right"
     }
   },
   "consequent": {
     "type": "argument reference",
     "reference": "left"
   },
   "alternative": {
     "type": "function call",
     "function": "add",
     "left": {
       "type": "function call",
       "function": "successor",
       "arg": {
         "type": "argument reference",
         "reference": "left"
       }
     },
     "right": {
       "type": "function call",
       "function": "predecessor",
       "arg": {
         "type": "argument reference",
         "reference": "right"
       }
     }
   }
 }
}
{
 "Z1K1": "Z14",
 "Z14K1": "Z144",
 "Z14K2": {
   "Z1K1": "Z7",
   "Z7K1": "Z31",
   "Z31K1": {
     "Z1K1": "Z7",
     "Z7K1": "Z145",
     "Z145K1": {
       "Z1K1": "Z18",
       "Z18K1": "Z144K2"
     }
   },
   "Z31K2": {
     "Z1K1": "Z18",
     "Z18K1": "Z144K1"
   },
   "Z31K3": {
     "Z1K1": "Z7",
     "Z7K1": "Z144",
     "Z144K1": {
       "Z1K1": "Z7",
       "Z7K1": "Z146",
       "Z146K1": {
         "Z1K1": "Z18",
         "Z18K1": "Z144K1"
       }
     },
     "Z144K2": {
       "Z1K1": "Z7",
       "Z7K1": "Z147",
       "Z147K1": {
         "Z1K1": "Z18",
         "Z18K1": "Z144K2"
       }
     }
   }
 }
}

if(
  is_zero(right),
  left,
  add(
    successor(left),
    predecessor(right)
  )
)

Z31(
  Z145(Z144K2),
  Z144K1,
  Z144(
    Z146(Z144K1),
    Z147(Z144K2)
  )
)

Diese Komposition beruht auf einer Anzahl anderer Z8/Funktionen: Z145/ist Null, Z146/Nachfolger, Z147/Vorgänger, Z31/if, und interessanterweise — es selbst. Es ist für eine Z14/Implementierung vollständig OK, ihre eigene Z8/Funktion recursiv aufzurufen. Beachte aber, dass die Auswertefunktion die Z14/Implementierung nicht rekursiv aufrufen muss - einer Auswertefunktion steht es frei, bei jedem Rekursionsschritt eine beliebige Implementierung zu wählen.

Auswertungsreihenfolge

Die Reihenfolge der Auswertung ist dem Auswerter überlassen. Da alle Z8/Funktionen keine Nebenwirkungen haben dürfen, wird dies immer zum gleichen Ergebnis führen. Eine unkluge Auswertestrategie kann aber dazu führen, dass viel mehr gerechnet wird als nötig oder dass der Auswerter gar nicht zu Ende kommt. Z1445/add recursive liefert uns ein Beispiel, das in einer Endlosschleife enden könnte, wenn wir eine vollständige Auswertungsreihenfolge versuchen:

Für den Aufruf von Z31/if in Z1445/add recursive wäre es unklug, zuerst alle drei Argumente auszuwerten und dann entweder das zweite oder das dritte Argument zurückzugeben. Abhängig vom ersten Argument Z31K1/Bedingung brauchen wir nur entweder Z31K2/konsequent oder Z31K3/alternative zurückzugeben. Es wird nie der Fall auftreten, dass wir sowohl das zweite als auch das dritte Argument auswerten müssen.

In der Tat könnten wir sogar das zweite oder dritte Argument unausgewertet zurückgeben. Denke daran, dass die Auswertefunktion ohnehin jedes Ergebnis erneut auswertet, bis ein Fixpunkt erreicht ist. Z31/if kann also locker implementiert werden, den irrelevanten Zweig weglassen und den relevanten Zweig als unbewertetes ZObjekt zurückgeben.

Eine lockere Auswertungsstrategie wird im Allgemeinen empfohlen, aber wenn der Auswerter z. B. eine Z16/Code-basierte Implementierung verwenden möchte, ist dies möglicherweise nicht machbar. Und dann könnte der Auswerter entscheiden, zuerst die Argumente und dann den äußeren Aufruf auszuwerten. Schließlich gibt es Möglichkeiten, mit verschiedenen Auswertungsstrategien zu experimentieren.

Z20/Tests

Z20/Tests sind ZObjekte, die einen Z7/function call aufrufen und dann das Ergebnis mit Hilfe der Z8/Funktion prüfen. Wenn die Z8/Funktion ein Z54/true zurückgibt, dann ist der Z20/Test bestanden, andernfalls durchgefallen.

Tests werden benutzt, um sicherzustellen, dass alle Z14/Implementatierungen sich wie gewollt verhalten. Sie sollten ähnlich wie Unit Tests betrachtet werden. Eine Z8/Funktion sollte alle Z20/Tests auflisten, die zur Sicherstellung der Vorgabenkonformität einer Z14/Implementierung durchlaufen werden müssen. Zusätzlich können die verschiedenen Z14/Implementierungen gegeneinander auf Konsistenz getestet werden.

{
 "type": "test",
 "call": {
   "type": "function call",
   "function": "add",
   "left": "two",
   "right": "two"
 },
 "check": {
   "type": "function call",
   "function": "curry right",
   "function to curry": "equal positive integer",
   "right": "four"
 }
}
{
 "Z1K1": "Z20",
 "Z20K1": {
   "Z1K1": "Z7",
   "Z7K1": "Z144",
   "Z144K1": "Z382",
   "Z144K2": "Z382"
 },
 "Z20K2": {
   "Z1K1": "Z7",
   "Z7K1": "Z79",
   "Z79K1": "Z150",
   "Z79K2": "Z384"
 }
}

Z1442/Add two and two weist ein interessantes Muster auf: die Funktion Z20K2/check wird on the fly durch Currying der Funktion Z150/equal positive integer erzeugt. Wir würden nicht eine Z8/Funktion mit dem Namen "gleich vier" schreiben wollen, sondern stattdessen die Funktion Z150/equal positive integer verwenden und eines ihrer Argumente mit einem konstanten Wert, in diesem Fall Z384/four, fixieren (curry). Wenn sie also ausgeführt wird, hat Z20K2/check den Wert einer dynamisch erstellten Z8/Funktion, die wiederum verwendet wird, um das Ergebnis des Funktionsaufrufs in Z20K1/call zu validieren.

Generische Typen

Ein generischer Typ wird durch einen Z7/Funktionsaufruf einer Z8/Funktion realisiert, die einige Argumente entgegennimmt und einen Z4/Typ zurückgibt.

Beispielsweise kann ein Z22/Paar mit zwei Z4/Typen parametriert werden, einem für das erste und einem für das zweite Element. Wenn wir also ein Paar von Z10070/Positive integers erzeugen wollen, würden wir Z22/Pair(Z10070/Positive Integer, Z10070/Positive integer) aufrufen. Das Ergebnis wäre ein Z4, das wir für das Z1K1-Feld eines ZObjekts verwenden können.

{
 "type": {
   "type": "function call",
   "function": "pair",
   "first": "positive integer",
   "second": "positive integer"
 },
 "first": "one",
 "second": "two"
}
{
 "Z1K1": {
   "Z1K1": "Z7",
   "Z7K1": "Z22",
   "Z22K1": "Z10070",
   "Z22K2": "Z10070"
 },
 "K1": "Z381",
 "K2": "Z382"
}

Das Ergebnis des Z7/Funktionsaufrufs ist ein dynamisch erzeugter Z4/Typ, was sicherstellt, dass die beiden Elemente des Paares den richtigen Z4/Typ haben. Das Ergebnis dieses Z7/Funktionsaufrufs sieht so aus:

{
 "type": "type",
 "identity": {
   "type": "function call",
   "function": "pair",
   "first": "positive integer",
   "second": "positive integer"
 },
 "keys": [
   {
     "type": "key",
     "id": "K1",
     "value type": "positive integer",
     "required": "true"
   },
   {
     "type": "key",
     "id": "K2",
     "value type": "positive integer",
     "required": "true"
   }
 ],
 "validator": "validate pair"
}
{
 "Z1K1": "Z4",
 "Z4K1": {
   "Z1K1": "Z7",
   "Z7K1": "Z22",
   "Z22K1": "Z10070",
   "Z22K2": "Z10070"
 },
 "Z4K2": [
   {
     "Z1K1": "Z3",
     "Z1K2": "K1",
     "Z3K1": "Z10070",
     "Z3K2": "Z54"
   },
   {
     "Z1K1": "Z3",
     "Z1K2": "K2",
     "Z3K1": "Z10070",
     "Z3K2": "Z54"
   }
 ],
 "Z4K3": "Z100702"
}

Dies erklärt auch das Z4K1/Identitätsfeld auf Z4/Typ: Es beschreibt, wie der Z4/Typ erstellt wurde, und ermöglicht uns den Zugriff auf die Argumente, die für die Typ-Erstellung verwendet wurden. Diese Information deklarativ zu halten ist sehr hilfreich, um einen Funktionsaufruf statisch zu validieren und um Typen zu vergleichen.

Wenn wir ein Z22/Paar wollen, das den Z4/Typ eines oder beider seiner Elemente nicht einschränkt, könnte man die Z22/Pair-Funktion mit Z1/ZObjekt als einem oder beiden Argumenten aufrufen.

Z10/Listen

Hier ist eine Liste mit zwei Zeichenketten:

(
[
 "a",
 "b"
]
[
 "a",
 "b"
]

Wenn wir sie in ZObjekte umwandeln, sieht sie wie folgt aus:

{
 "type": {
   "type": "function call",
   "function": "list",
   "elementtype": "string"
 },
 "head": "a",
 "tail": {
   "type": {
     "type": "function call",
     "function": "list",
     "elementtype": "string"
   },
   "head": "b",
   "tail": "nil"
 }
}
{
 "Z1K1": {
   "Z1K1": "Z7",
   "Z7K1": "Z10",
   "Z10K1": "Z6"
 },
 "K1": "a",
 "K2": {
   "Z1K1": {
     "Z1K1": "Z7",
     "Z7K1": "Z10",
     "Z10K1": "Z6"
   },
   "K1": "b",
   "K2": "Z13"
 }
}

Beachte, dass bei der Deserialisierung eines JSON-Array-Literals zuerst geprüft werden muss, ob alle Elemente des Arrays den gleichen Z1K1/Typ haben. Falls ja, erzeugen wir eine Z10/Liste mit Nutzung dieses Typs als Argument. Falls nicht, erzeugen wir ein Z10 mit Z1/ZObject als Argument.

Z8/Funktionstypen

Funktionen verwenden den gleichen Mechanismus wie generische Typen, um konkrete Funktionstypen zu sein. D.h. Z8/Function ist nicht der Z4/Type einer Funktion, sondern ist selbst eine Funktion, die einen Z4/Type erzeugt, der für eine Funktion verwendet werden kann.

Bei Z144/add haben wir bisher das Feld Z1K1/Typ übersprungen. Hier ist das Feld.

{
 "type": {
   "type": "function call",
   "function": "function",
   "return type": "positive integer",
   "argument types": [
     "positive integer",
     "positive integer"
   ]
 },
 ... 
}
{
 "Z1K1": {
   "Z1K1": "Z7",
   "Z7K1": "Z8",
   "Z8K1": "Z10070",
   "Z8K2": [
     "Z10070",
     "Z10070"
   ]
 },
 ... 
}

This will return the Z4/Type that also contains its return and argument types.

Wenn Sie einen Z4/Typ für eine Z8/Funktion benötigen, die mit beliebigen Z8K1/Rückgabetypen und Z8K2/Argumenttypen arbeitet, dann würden wir Z1/ZObjekt für Z8K1/Rückgabetyp oder die leere Liste als Z8K2/Argumenttyp verwenden. Dies ist z. B. für eine Z8/Funktion nützlich, die dir die Anzahl der Argumente mitteilt, die eine Z8/Funktion hat. Wenn Sie bereits die Anzahl der Argumente kennen müssten, um die richtige Funktion auszuwählen, wäre dies offensichtlich ziemlich nutzlos.

Der Validator auf Z4/Function stellt sicher, dass die Typen, wie sie beim Aufruf von Z7/Function an Z8/Function angegeben werden, mit den Typen, die bei der Z4K2/Schlüsseldeklaration angegeben werden, konsistent sind.

Z5/Fehler

Ein Z7/Function call kann immer auf einen Z5/Error laufen. Dies kann für mehr oder weniger unbehebbbare Fälle sein (d.h. Division durch Null oder Speicherüberlauf werden beide gleich behandelt). Ein Z5/Error ist ein generischer Typ, der mit einem Z15/Error-Typ aufgerufen wird.

Wenn ein Z5-Fehler als Argument für einen Z7-Funktionsaufruf angegeben wird, wird der Z5-Fehler normalerweise eingepackt, um eine Nachverfolgung zu ermöglichen, und weitergegeben. Die Auswertefunktion wird die Z14/Implementierung nicht einmal aufrufen.

Wenn eine Z8/Funktion einen Z5/Fehler erwischen will, muss sie in ihrer Signatur explizit alle Z15/Fehlertypen auflisten, die sie erwischen will. Wenn dann ein Z5/Fehler der aufgelisteten Z15/Fehlertypen auftritt, wird die Z14/Implementierung trotzdem aufgerufen und kann nun den Z5/Fehler behandeln.

Ein Z5/Error ist eine Instanz vom Z4/Type Z5/Error(Z15/Error type). Der resultierende Type hat weitere Z3/Keys, wie durch den Typ Z15/Error spezifiziert, um nützliche Informationen über den Z5/Error zu erhalten.

Beispiel: Wenn Sie die Funktion Z157/Division mit einer Z380/Null für den Z157K2/denominator aufrufen, würden Sie das folgende Objekt zurückbekommen: Z5/error(Z442/Division_durch_Null)(Z384/four)

Nicht-funktionale Funktionen

Während keine Z8/Funktion Nebenwirkungen haben darf, können einige Z8/Funktionen nicht vollständig funktional sein. D.h. sie können unterschiedliche Werte zurückgeben, wenn sie mit den gleichen Parametern aufgerufen werden. Typische solche Z8/Funktionen sind "Rückgabe einer Zufallszahl", "Rückgabe der aktuellen Uhrzeit" oder "Rückgabe eines Wertes aus Wikidata".

Wir sehen auch, dass es sinnvoll wäre, nicht nur zu sagen, ob eine Z8/Funktion funktional ist oder nicht, sondern tatsächlich ein Caching-Regime für jede Z8/Funktion anzugeben.

Alle diese Z8/Funktionen müssen gekennzeichnet werden, und keine Z8/Funktion, die voll funktional ist, darf eine Funktion aufrufen, die nicht funktional ist. Die Auswertefunktion muss sich vergewissern, dass diese Bedingung erfüllt ist, da sonst die Ergebnisse verfälscht oder anderweitig unerwartet sein könnten.

Dies wird in einem späteren Dokument behandelt.

Zx/Sum types

Ein besonders nützlicher generischer Typ ist der Typ Zx/Sum, der eine Liste von Z4/Typen annimmt und einen Z4/Typ zurückgibt, der genau eine Instanz eines beliebigen der angegebenen Typen annehmen kann.

Dadurch werden auch nicht benötigte Parameter in Funktionsaufrufen ermöglicht.

Dies wird in einem späteren Dokument behandelt.

REPL (EVA-Zyklus)

Dies entspricht der Definition von REPL: Read–eval–print loop (EVA: Eingeben, Verarbeiten, Ausgeben):

  • Die REPL, oder Befehlszeilenschnittstelle, nimmt als Eingabe Z6/string entgegen.
  • Es führt dann eine Z8/Funktion über diese Eingabezeichenfolge aus, die die Eingabe parst und vorverarbeitet und ein ZObjekt zurückgibt.
  • Dieses ZObjekt wird durch den Validator vom Z4/Type des ZObjekts validiert. Wenn es gültig ist, wird das Ergebnis dann ausgewertet, bis es einen Fixpunkt erreicht.
  • Dann ruft das REPL eine Z8/Funktion auf, um das resultierende ZObjekt in einen Z6/String zu linearisieren. Dieser String wird dann dem Benutzer angezeigt oder ausgedruckt.
  • Und die REPL fragt nach neuen Eingaben.

Eine gute Wikifunctions-REPL sollte immer ermöglichen:

  • die Parse-Funktion setzen
  • die Validierung an- oder abschalten
  • die Auswertung an- oder abschalten
  • die Linearisierungsfunktion setzen

Dies ermöglicht viele interessante Muster. Zum Beispiel könnte es Parser für viele verschiedene Syntaxen geben. Einfache Funktionsaufrufsyntaxen, wie wir sie oben gesehen haben, eine Haskell-ähnliche Syntax ohne viele Klammern, eine Syntax, die näher an der Mathematik ist und Infix-Operatoren verwendet, Übersetzungen von Labels zu ZIDs, oder ganz domänenspezifische Syntaxen. Der Parser kann auch einen Präprozessor enthalten. Ein Beispiel für die Vorverarbeitung ist das automatische Hinzufügen von Typ-Zwangsbedingungen, um das Schreiben von Z7/Funktionsaufrufen zu erleichtern, oder die Angabe der Z14/Implementierung bei einer Z8/Funktion und damit die Erzwingung der Verwendung einer bestimmten Z14/Implementierung durch die Auswertefunktion.

Ebenso kann der Linearisator sehr flexibel auf einen bestimmten Anwendungsfall abgestimmt werden. Parser und Linearisierer werden Z8/Funktionen in Wikifunctions sein, was es jedem erlaubt, neue Parser und Linearisierer zu erstellen und sie in einer Vielzahl von Schnittstellen zu verwenden, was interessante Nutzungsmuster ermöglicht.

Referenz-Index zentraler Z4/Typen und ihrer Z3/Schlüssel

  • Z1/ZObject
    • Z1K1/type (Z4/Type)
  • Z2/Persistent object
    • Z2K1/id (Z6/string)
    • Z2K2/value (Z1/ZObject)
    • Z2K3/label (Z12/Multilingual text)
  • Z3K1/Key
    • Z3K1/value type (Z4/Type)
    • Z3K2/key id (Z6/String)
    • Z3K3/label (Z12/Multilingual text)
  • Z4/Type
    • Z4K1/identity (Z4/Type)
    • Z4K2/keys (Z10/List(Z3/Key))
    • Z4K3/validator (Z8/Function(...))
  • Z5/Error
    • Z5K1/error type
  • Z6/String
    • Z6K1/string value (Z6/String)
  • Z7/Function call
    • Z7K1/function (Z8/Function)
    • Others based on Z8/Function
  • Z8/Function (generic)
    • K1/arguments (Z10/List(Z17/Argument declaration))
    • K2/return type (Z4/Type)
    • K3/tests (Z10/List(Z20/Test))
    • K4/implementation (Z14/Implementation)
    • K5/identity (Z8/Function)
  • Z9/Reference
    • Z9K1/reference ID (Z6/String)
  • Z10/List (generic)
    • K1/head
    • K2/tail

Hauptunterschiede zu AbstractText

  • Nur Z7/Funktionsaufrufe werden ausgewertet. Alles andere wird nicht ausgewertet.
  • Einführung generischer Typen
  • Z10/String und Z22/Pair sind jetzt generische Typen.
  • Z8/Function ist ein generischer Typ, basierend auf den Typen der Eingaben und Ausgaben.
  • Ein K5 den Z8s hinzugefügt
  • Z3K2 von validator auf keyid und Z3K3 von required auf label geändert, Z3K4-Z3K6 hinzugefügt
  • Z4K4-Z4K7 entfernt
  • Geringfügige Änderung von Z4K1
  • Alle Schlüssel auf Z14/Implementierung geändert, Z19/builtin losgeworden
  • Umwandlung von Z20/Test in ZObjekte der obersten Ebene anstelle von Unterobjekten von Z8/Function
  • Durch Verwendung von autoquote auf Z20/Test die Z21/Argumentenlist losgeworden.
  • Umwandlung von Z14/Implementation in ZObjekte der obersten Ebene anstelle von Unterobjekten von Z8/Function
  • ZID von Pair wurde von Z2 in Z22 geändert (um dem Z2/Persistent object Raum zu geben)
  • Z5/Error und Z15/Exception wurden zu Z5/Error zusammengefasst
  • Z5/Error ist jetzt generic type.
  • Verhalten von REPL spezifiziert
  • Herauslösung von Z2/Persistent object aus Z1
  • Z17K2/Keyid und Z17K3/Label zu Z17 hinzugefügt
  • Built-Ins können jetzt ihre ZID ändern

Ein paar Fragen und zu erledigende Aufgaben

  • Brauchen wir irgendwo am Anfang "muss/optional" für Schlüssel? — Nein.
  • Voreinstellungen auf Z3/Schlüssel durch Zx/Summe ersetzen? (Oder zumindest mit Z17/Argumentendeklaration konsistent machen)
  • Könnte auf später aufgeschoben werden, falls wir die Voreinstellung auf Z3 im Moment nicht brauchen.
  • Z2K4 vorläufig entfernen?
  • Z16/Implementierung nach Programmiersprache unterscheiden?
  • Wenn Fehler ihre eigenen Felder haben können, sollten Programmiersprachen das auch
  • Mache es generisch in Bezug auf die Programmiersprache. Das ist etwas für später.

Siehe auch