Lua-Unterstützung für Vorlage:Personendaten.

Griot-Plugin für Sortierung römischer Zahlen 2016/2017

Bearbeiten

von Januar 2017 – Zeilennummerierung nach dieser Version.

Die nachstehenden Punkte müssen verbessert werden, um das Modul in einen wartungsfähigen und produktiv einsetzbaren Zustand zu bringen.

  • Besser noch: Neuentwicklung als Modul:Vorlage:Personendaten/sortRoman – inzwischen sind weitere Plug-Ins vorstellbar geworden, namentlich zwecks Parameteranalyse.

Reihenfolge der Funktionen

Bearbeiten
  • Bei allen interpretierten Sprachen, darunter auch Lua und JavaScript, müssen die Funktionen so angeordnet werden, dass zunächst die am tiefsten liegenden Funktionen deklariert werden, danach diejenigen, die die bereits deklarierten Funktionen aufrufen, damit sie an dieser Stelle bereits bekannt sind.
  • So wird das auch in Lua-Modulen gemacht: Erst die lokalen Basis-Funktionen, danach immer höher, ganz zum Schluss kommt die Schnittstelle von außen nach innen, damit diese die lokalen Funktionen kennt.
  • Nur bei rein auf Kompilierunng ausgerichteten Sprachen ist das anders.
  • Hier stehen die Deklarationen und die Implementierungen durcheinander:
    • Die Deklaration von HandleRomanNumeral steht isoliert in Zeile 8.
    • Die abgerissene Implementierung dazu beginnt in Zeile 188 und endet 350 Zeilen später in 534.
    • Bereits in Zeile 10 steht der zentrale Aufruf dazu, der von der Schnittstelle nach draußen aufgerufen wird. Diese Funktion RomanNumeralInPD gehört jedoch an das Ende, dann wäre ihr HandleRomanNumeral bekannt und das Zerreißen von deren Deklaration und Implementierung wäre nicht erforderlich und Funktionen können geschlossen in einem Stück hingeschrieben werden.
  • In seltenen Fällen bei reziprokem wechselseitigen Aufruf wären andere Lösungen erforderlich. Das ist hier nicht der Fall.

Globale Deklarationen geschlossen zu Beginn des Moduls

Bearbeiten
  • Nach der schon beschriebenen unnötigen gesonderten Deklaration der HandleRomanNumeral kommt die Umsetzung der Funktion RomanNumeralInPD.
  • Erst danach tauchen plötzlich drei global für den gesamten Chunk genutzte Variablen auf:
    local test   = false      -- run in test mode?
    local fname  = "?"        -- function name, for error messages
    local errors = {[0] = ""} -- table of error messages (with sentinel)
  • Sie sind zwischen zwei Funktionsdeklarationen versteckt.
  • Das ist Murks. Zunächst werden (in allen Programmiersprachen) geschlossen und übersichtlich alle für die gesamte Programmeinheit gültigen Einzelvariablen im Modulkopf vereinbart; erst danach kommen die Funktionen bzw. Methoden.
  • Bei den „global“, also zumindest innerhalb des Modul-Chunks global und über Einzelfunktionen hinaus deklarierten Variablen ist es nebenbei üblich, sie zumindest mit Großbuchstaben beginnen zu lassen, um ihre besondere Bedeutung kenntlich zu machen, während die auf eine Einzelfunktion begrenzten lokalen Variablen immer nur mit Kleinbuchstaben beginnen und damit für jeden hinzu kommenden Programmierer sofort die Auswirkungen klar werden.

Riesiger Einschub innerhalb einer Funktion

Bearbeiten
  • Mitten innerhalb der HandleRomanNumeral gibt es plötzlich, nach bereits 60 Zeilen, von 248 bis 446 einen fast 200 Zeilen langen Nebensatz, die Funktion handlePrn.
  • Diese Taktik wendet man an, wenn man sich vor unbefugten Nutzungen von außen schützen will, und den Zugriff durch Angreifer unterbinden muss.
  • Hier gibt es keine Angreifer. Es ist ohnehin nur alles innerhalb des Plug-Ins zugreifbar, und die HandleRomanNumeral schützt sich gegen missbräuchliche Nutzung durch eine der anderen sieben Funktionen, über die jedoch die volle Kontrolle besteht.
  • Sowas kann man auch mal mit einer nachrangigen Hilfsfunktion von drei oder fünf Zeilen machen, die wie ein Makro eine kleine Privatangelegeneheit ausführt. Aber nicht mit 200 Zeilen mittendrin und das als wesentliche und zu pflegende Konfiguration.
  • Eine solche Funktion gehört gleichbereichtigt auf ein Niveau mit allen anderen.

local-Deklarationen im Kopf der Programmeinheit bündeln

Bearbeiten
  • Es gibt für Lua keine linter, keine Analyse-Werkzeuge, von denen man einfach und robust über mögliche Programmfehler und fehlende Deklarationen und Ursachen unauffindbaren Fehlverhaltens informiert werden würde.
  • Umso wichtiger ist es, übersichtlich alle local-Deklarationen am Beginn jedes Blocks zusammenzuhalten, für den sie gelten sollen.
  • Sie geben Auskunft über alle in diesem Block deklarierten Variablen, und zusammen mit den auf den jeweils höheren Ebenen (die ihrerseits wieder immer zu Beginn ihrer Einheit stehen) weiß man dann auf Anhieb, welche Variablen in welcher Einheit bereits deklariert sind, bei welchen das womöglich vergessen worden war, und über welchen Bereich hinweg sie gültig sind.
  • Hier sind die munter mittenrein gestreut, nach 280 Zeilen wird urplötzlich noch ein usedAlready in Zeile 464 deklariert, während es zu Beginn von HandleRomanNumeral keine übersichtliche Aufzählung aller auftretenden Variablen gibt.
  • Deklarationen von Einzelvariablen am Anfang von Programmeinheiten vor der ersten ausführbaren Anweisung zusammenzuhalten, selbst wenn die Sprache das auch erst später erlauben würde, ist ein in praktisch allen Programmiersprachen gängiges Prinzip.

Trennung von Programm und Daten

Bearbeiten
  • Die unzähligen Regeln für die enzyklopädischen Lemmata und für welche Jahrhunderte sie anzuwenden sind und für welche Adelstitel und Berufsgruppen sie gelten sollen, sind ab Zeile 276 tief drinnen in funktionaler Programmierung versteckt.
  • Dies muss ganz am Anfang stehen und in ihrer fachlich-inhaltlichen Bedeutung entsprechend der Lemmata von möglichst vielen Programmierern gepflegt werden können.
  • Das ist zurzeit schlicht nicht durchschaubar.
  • Etwa
    • lon ~= " Florencia de la V "
    • lon ~= " Prince Far I "
    • But note William Wrigley junior II.
    • But with one exception: James I. Roosevelt
    • lon ~= " Haskell V. Anderson III " )
    • if krz:find( "Patriarch" ) then
    • year( geb ) <= 1810
    • lon:sub(ixA - 3, ixA) ~= "sz. "
    • lon:sub(ixA - 3, ixA) ~= "dr. "
  • Sowas wird auch nicht als Funktionsprogrammierung angelegt, wenn irgend vermeidbar, sondern als statische atomare Daten.
  • Siehe etwa Modul:Vorlage:DtRechtswörterbuch oder Modul:TemplateData – dort stehen zu Beginn alle fachlichen Einzeldaten, und wenn Anpassungen notwendig würden, dann lässt sich hier die Nummer von Heften oder eine ISBN oder ein Farbwert oder Schlüsselwort noch relativ gefahrlos ändern.
  • Erst danach folgen die Funktionen mit der eigentlichen Programmierung, von denen man besser die Finger lässt, wenn man nicht ganz genau weiß, was man tut.
  • Heißt hier die Definition einer kleinen Regelsprache, etwa wie folgt (nur Prinzip):
Rules = { { numAb     = 76 },
          { patDescr  = "Patriarch",
            result    = true },
          { patName   = "^James I. Roosevelt" },
          { patName   = "^Haskell V. Anderson III" },
          { patName   = "^Crooked I" },
          { patName   = "[ (]junior[ ),]" },
          { patName   = "[ (]senior[ ),]" },
          { patName   =   "%(Junior%)"    },
          { patName   =   "%(Senior%)"    },
          { patName   = "[ (][JjSs]r[ .),]" },
          { patName   = "[ (][Jj]un%." },
          { patName   = "[ (][Ss]en%." },
          { patPrefix = "sz%. $" },
          { patPrefix = "dr%. $" },
          { patSuffix = "^%. D[eu] " },
          { patSuffix = "^%. Le " },
          { patSuffix = "^%. Van " },
          { gebBis    = 1810,
            result    = true }
        }
  • Die Elemente von Rules werden eins nach dem anderen angewendet.
    • Jede kann eine oder mehrere Komponenten enthalten, die auf die Kurzbeschreibung, den PD-Namen, das Lemma, das Geburtsdatum angewendet werden.
    • Die mit pat sind pattern und die mit geb Zahlen für ein numerisches Jahr, oder num die Zahl und der Rest erklärt sich selbst.
    • Sollte eine Regel mit allen ihren Komponenten zutreffen, gilt das result für die Entscheidung.
    • Wenn aber kein result angegeben ist, bedeutet das automatisch result=nil und damit ist keine Konvertierung vorzunehmen.
    • Sobald eine Regel vollständig angeschlagen hatte, egal mit welchem Ergebnis, wird diese Regelprüfung abgebrochen.
  • Man hält grundsätzich die Funktionsprogrammierung frei von spezifischen Angelegenheiten einzelner Anwendungsfälle. Entweder ist das allgemeingültig und eine Veränderung nicht zu erwarten, oder die Anpassungsnotwendigkeit ist von vornherein absehbar. Im letzteren Fall hat es nicht direkt in die Funktionsprogrammierung verstrickt zu sein.
  • Die Feinheiten, in welcher menschlichen Sprache welche Abkürzungen und Muster im Lemma, im PD-Namen, in der PD-Beschreibung vorkommen könnten, gehören definitiv zu dem, was immer wieder angepasst werden muss.
  • #Regelsatz abarbeiten ist die zugehörige Implementierung.
  • Ach ja, last but not least, diese Funktion fair() wertet genau nach diesem Prinzip solch einen Regelsatz Config.warnings aus.

Keine Einzelfälle im Lua-Modul

Bearbeiten
  • Die veränderte PD-Vorlage soll einen neuen Parameter SORTIERUNG= bekommen.
  • Damit können alle Autoren unbürokratisch und ohne irgendwelche Lua-Programmierer bemühen zu müssen sofort den richtigen Schlüssel zuweisen und die Lua-Programmierung versucht nicht einmal ansatzweise, sich einen auszudenken.
  • Ich war immer schon strikt gegen den Griot-Ansatz, durch einen superintelligenten allumfassenden Universal-Algorithmus jede Nuance programmatisch auszurechnen.
  • Dass das wie vorhergesagt am Ende doch nicht klappt, zeigen beispielsweise die explizit in Zeile 394, 419, 425 ganz tief drinnen versteckten Ausnahmeregeln unter anderem für
  • Auch eine Interpretation von Hubert L. L. Busard ist müßig. Manuell SORTIERUNG= und Ende Gelände.

Wiki-Aufrufe verwenden

Bearbeiten
  • Zeile 52 enthält einen Aufruf print().
  • Diese Funktion ist für einen Wiki-Kontext nicht zugesicherrt.
  • Bei uns heißt sie mw.log().
  • Zufällig funktioniert sie momentan vermutlich, stürzt zumindest nicht ab, das ist jedoch nicht gewährleistet.
  • Mediawiki dazu: It was decided that it should be omitted in favour of return values, to improve code quality. If necessary, mw.log() may be used to output information to the debug console.
  • Die Funktion gehört zu einer Lua-Installation, die in unserem Fall jedoch auf dem Wiki-Server abläuft, und inwieweit das in die HTML-Seite einfließen würde, ist mehr als fraglich, und das Ergebnis, das ausgegeben werden soll, bekäme man möglicherweise nie zu sehen.

Keine Parameterprüfung

Bearbeiten
  • Das Römische-Zahlen-Sortierungs-Plugin kann überhaupt nur aufgerufen werden, wenn das allgemeine Modul den Eindruck hat, dass es römische Zahlen im NAME geben würde.
  • Das aufrufende Modul weiß auch sehr genau, dass das Lemma existiert, wie es heißen würde und ist sich ganz sicher, dass das ein string ist.
  • Wenn es denn Design by contract sein soll, dass das Römische-Zahlen-Sortierungs-PlugIn nur aufgerufen werden darf, wenn sowohl KURZBESCHREIBUNG als auch GEBURTSDATUM nicht-leere Zeichenketten wären, dann prüft allenfalls das aufrufende Modul die letzten beiden Bedingungen, und ruft von vornherein das PlugIn nicht auf.
  • Wobei nur in wenigen Fällen diese Angaben tatsächlich benötigt würden; die Ermittlung des Sortierschlüssels könnte genauso gut geschehen, wenn diese Werte nicht gesetzt sind, und dann greift hier ggf. die Regelprüfung nicht oder gibt einen Default-Wert vor (geboren 2000, Kurzbeschreibung ist leere Zeichenkette).
  • Auf jeden Fall ist die erneute Datenprüfung nebst Fehlermeldung überflüssig, bläht den Code weiter auf, bringt nichts und schadet der Performance und Übersichtlichkeit.
  • Unbeschadet dessen muss das aufrufende Modul jedoch eine Direktive erhalten, was es denn nun machen soll, wenn in der Vorlageneinbindung mal kein Geburtsdatum oder keine Kurzbeschreibung angegeben wurde. Das weiß es nach momentaner Vorgehensweise des Plug-In aber immer noch nicht. Diese Intelligenz sollte jedoch im Plug-In gebündelt sein.

Standard-Bibliotheksfunktion nutzen

Bearbeiten
  • Wir haben eine Standard-Funktion roman2number.
  • Das aufrufende Modul hat auch heute bereits die Bibliothek bereit.
  • Es wird zukünftig die FormatNum übergeben, aus der FormatNum.roman2number() aufgerufen werden kann.
  • Damit kann der Block Zeile 134–184 mit 50 Zeilen und der RomanNumeralToNumber entfallen.
  • Wir sind daran interessiert, eine einzige projektweite oder gar globale Bibliotheksfunktion zu verwenden, die sauber funktioniert und zentral gepflegt werden kann, um Standardaufgaben zu erfüllen.

Regelsatz abarbeiten

Bearbeiten

Der nachstehende Code kann die unter #Trennung von Programm und Daten umrissene Regelsprache auswerten.

  • Er ersetzt rund 200 Zeilen Griot-Funktion, wozu noch der obige Regelsatz käme.
  • Pflegende PD-Experten brauchen sich nicht in Zeile 438 mit Lua-Statements herumplagen, sondern bearbeiten nur den überschaubaren Regelsatz.
  • Der nachstehende Algorithmus muss einmal durchgetestet werden und bleibt danach unverändert.
  • Ungetestet, aus dem Zusammenhang gerissen, müsste aber in etwa funktionieren.
local current = { Lemma  = Lemma,
                  Name   = prnInfoTable.lon,
                  Descr  = Kurz,
                  Prefix = prnInfoTable.lon:sub( 1, prnInfoTable.ixA ),
                  Suffix = prnInfoTable.lon:sub( prnInfoTable.ixZ ),
                  num    = value,
                  geb    = year( Geburt )  or  2000
                }
local n, result, rule, s
for i = 1, #Rules do
    rule = Rules[ i ]
    for k, v in pairs( rule ) do
        s = type( v )
        if s == "string" then
            if k:sub( 1, 3 ) == "pat" then
                s = current[ k:sub( 4 ) ]
                if type( s ) == "string" then
                    if s:find( v ) then
                        result = rule.result or false
                    else
                        result = nil
                        break -- for k, v
                    end
                else
                    error( "Personendaten/sortRoman * Bad rule: " .. k )
                end
            end
        elseif s == "number" then
            s = k:match( "^(%l+)%u" )
            n = current[ s ]
            if type( n ) == "number" then
                if k:find( "Bis", 2, true ) then
                    n = v
                    v = current[ s ]
                end
                if n < v then
                    result = rule.result or false
                else
                    result = nil
                    break -- for k, v
                end
            else
                error( "Personendaten/sortRoman * Bad rule: " .. k )
            end
        end
    end -- for k, v
    if type( result ) == "boolean" then
        break -- for i
    end
end -- for i

VG --PerfektesChaos 11:18, 30. Apr. 2018 (CEST)[Beantworten]