Autor Thema: Covella - Fantasy-Kalender - DSL für beliebige Systeme von Zeitmessung  (Gelesen 13226 mal)

0 Mitglieder und 1 Gast betrachten dieses Thema.

Offline 1of3

  • Richtiges Mädchen!
  • Titan
  • *********
  • Proactive Scavenger
  • Beiträge: 18.991
  • Username: 1of3
    • Sea Cucumbers and RPGs
Das hat wenig mit Scala oder Java zu tun. Die Frage ist, wie die Funktionen geschrieben sind. ;)

Du hast allerdings nicht unrecht, ich hatte nur vier Extratage, also hieße es dann:

val standardYear = (quartal *4 + Days(1) as "extra day") as "year"
val leapYear = (quartal * 4  + Days(1) as "extra day" + Days(1) as "leap day" ) as "year"

Zur Erklärung: Das "as" fasst was davor kommt zu einer Zeiteinheit zusammen mit dem folgenden Bezeichner zusammen. Was die Operatoren tun, hängt davon ab, ob zwischendurch ein "as" fällt. Dadurch wird also gesteuert, was "+" tut.

Zur Verdeutlichung bezeichne ich nicht ausgezeichnete Einheiten mit Großbuchstaben (A, B, C),  ausgezeichnete - d.h. solche, die "as" bekommen haben - mit Großbuchstaben und Apostroph (A', B', C').

Es gilt: A' + B' => (A', B'). D.h. die Summe zweier ausgezeichneter Einheiten ist ein Compound, das sie enthält.
Wenn man nun ein Compound hat und addiert noch ein ausgezeichnetes Element, was soll passieren? - Das drite Element soll mit in die Klammer:
(A', B') + C' => (A', B', C').

Wenn man jetzt zwischendurch wieder "as" schreibt, passiert folgendes: (A', B')'. Das zusammengesetzte Element ist also ausgezichnet und damit abgeschlossen. (Intern ändert "as" den Datentyp.) Wenn man jetzt auf diesem ausgezeichneten Element etwas addiert, ist das Ergebnis anders: (A', B')' + C' => ( (A', B')', C' ). Es hat also ein weiteres Level erzeugt.

Offline Skeeve

  • Hero
  • *****
  • Beiträge: 1.625
  • Geschlecht: Männlich
  • Username: XoxFox
Das hat wenig mit Scala oder Java zu tun. Die Frage ist, wie die Funktionen geschrieben sind. ;)

Naja, um die Funktionien zu verstehen... hätte es auch eine andere Uhrzeit sein müssen. Aber nun macht sich hier auch langsam die Erkenntnis breit dass "as" und "named" gar keine Elemente von Java oder Scala sind, sondern selbstgemachte Methoden einer abstrakten Klasse (oder doch Funktionen?)

Aber mein Erkenntnissgewinn ist hier ja nicht Thema, also weiter machen....  ;)
... oft genug sind die Spieler die größten Feinde der Charaktere, da helfen auch keine ausgeglichenen Gegner

Hoher gesellschaftlicher Rang ist etwas, wonach die am meisten streben, die ihn am wenigsten verdienen.
Umgekehrt wird dieser Rang denen aufgedrängt, die ihn nicht wollen, aber am meisten verdienen. [Babylon 5]

Offline 1of3

  • Richtiges Mädchen!
  • Titan
  • *********
  • Proactive Scavenger
  • Beiträge: 18.991
  • Username: 1of3
    • Sea Cucumbers and RPGs
Och, ich erklär das gern. Aber du hast recht. Dass man diesen Unterschied nicht sofort erkennt, ist eine Eigenschaft von DSL-freundlichen Sprachen: Keine Semikola, Funktionsaufrufe brauchen keine Klammern, Zugriff auf Eigenschaften und Methoden brauchen keine Punkte. Das gleiche könnte man wohl auch in Groovy und einigen anderen modernen Sprachen machen. Und ja, es sind Instanz-Methoden, Scala kann keine Toplevel-Funktionen und statische Methoden werden durch Methoden auf Singletons ersetzt.

Aber kommen wir mal zurück:

Ich würde für Konvenienz neben Days auch direkt Month einführen.

Month(int) - gib mir einen Monat mit int Tagen.
Month(int,String) - gib mir einen Monat mit int Tagen und Namen String.

Wir sollten auch die typischen Bezeichnungen für Tag, Woche, Monat, Jahr direkt als internationalisierten String einfügen. Dann kann sich die Kundschaft für typische Fälle die Anführungszeichen sparen. Internationalisierte Strings gibts mit der Bibliothek Rapture wohl recht komfortabel, hab ich aber noch nicht ausprobiert.


Zum Format:

Das könnten wir mit einem handgemachten Stringkontext machen. Das sähe bei der Verwendung dann z.B. so aus:

date"""$number(day). $name(month) $number(year)"""
Wenn man in den Aufrufen keine Anführungszeichen verwendet, kann man zur Berandung auch einmal, statt dreimal die Anführungszeichen machen. Hinter den Kulissen wird ein String mit einem solchen Präfix wie date als StringContext behandelt und der Compiler sucht dann eine Methode namens date.

Die Methode sollte dann ein DateFormat zurückgeben, das hat:
- eine Methode "to" um ein Datum zum String zu machen.
- eine PartialFunction "from" um aus einem String ein Datum zu machen.

Der Kalender hält eine Liste mit DateFormats vor. Wenn der User dann einen String parsen will, werden alle partiellen Funktionen durchlaufen, ob eine passt.

Offline Skeeve

  • Hero
  • *****
  • Beiträge: 1.625
  • Geschlecht: Männlich
  • Username: XoxFox
Dass man diesen Unterschied nicht sofort erkennt, ist eine Eigenschaft von DSL-freundlichen Sprachen: Keine Semikola, Funktionsaufrufe brauchen keine Klammern, Zugriff auf Eigenschaften und Methoden brauchen keine Punkte.

Ja! Man kann Python zwar recht großzügig umbauen und verbiegen , aber ganz so frei wie bei Scala wird man es wohl nicht hinbekommen...
Aber ich habe gerade ein Beispiel entdeckt. Ich glaube aus val leapYear = (quartal * 4  + Days(1) as "extra day" + Days(1) as "leap day" ) as "year" könnte man z.B. leapYear = (quartal * 4  + Days(1) |as| "extra day" + Days(1) |as| "leap day" ) |as| "year" machen und das wäre ein gültiger Python-Ausdruck den man durch den Python-Parser jagen könnte und bei passender Metaprogrammierung würde das Teil dann auch das gewünschte machen.
Oder man baut sich seinen eigenen speziell an die neue Sprache angepassten Parser. Das geht fast immer,  aber ob man den Aufwand treiben möchte.
... oft genug sind die Spieler die größten Feinde der Charaktere, da helfen auch keine ausgeglichenen Gegner

Hoher gesellschaftlicher Rang ist etwas, wonach die am meisten streben, die ihn am wenigsten verdienen.
Umgekehrt wird dieser Rang denen aufgedrängt, die ihn nicht wollen, aber am meisten verdienen. [Babylon 5]

Offline 1of3

  • Richtiges Mädchen!
  • Titan
  • *********
  • Proactive Scavenger
  • Beiträge: 18.991
  • Username: 1of3
    • Sea Cucumbers and RPGs
Mir sind noch ein paar Fragen zur Benutzung gekommen:

Bei einigen Zeiteinheiten fangen wir beim Zählen bei Null an. Es kann z.B. "0 Uhr 5 Minuten" sein. Aber egal, ob die Woche bei Sonntag oder Montag anfängt, es ist der erste Tag. Ebenso haben wir keinen nullten Tag im Monat und keinen nullten Monat im Jahr.

Wenn jetzt also jemand Datum#get(month) bzw. Datum#get(hour) sagt, erwartet er da ggf. unterschiedliche Ergebnisse. Jetzt gibts zwei Möglichkeiten: Entweder in der Dokumentation steht: "Die Rückgaben fangen bei 0 an zu zählen, achte drauf ggf. was draufzuaddieren, wenn du das Ergebnis benutzt!", oder wir erweitern das Modell, das man das Verhalten einstellen kann. Auch dann fragt sich, was der Standard sein soll, wenn man nichts einstellt: 0 oder 1?

Was findet denn die geneigte Userschaft sinnvoller?



Damit verwandt: Wir sagen zwar, dass wir unsere Jahre zählen, aber wir zählen sie nicht so, wir wir alles andere Zählen. Ein Jahr 0 gibts nicht und die davor sind auch nicht negativ. An dieser Stelle würde ich aber so rigoros sein und zu sagen: Das sind keine Zahlen. Das sind Jahresnamen. Das Jahr heißt "1980 n. Chr." bzw. "43 v. Chr." oder wie mans gern mag. Wer an der Stelle also get(year) statt getName(year) abfragt, bekommt nicht das gewünschte.

Offline Antariuk

  • Legend
  • *******
  • Beiträge: 4.797
  • Geschlecht: Männlich
  • Username: Antariuk
    • Plus 1 auf Podcast
Ich glaube ich fände es einfacher wenn eine Zählung standardmäßig bei 1 beginnt und ich als Option dann einstellen kann das es alternativ doch 0 sein soll.
Kleiner Rollenspielstammtisch: Plus 1 auf Podcast

"Ein Zauberer mag noch so raffiniert sein, ein Messer im Rücken wird seinen Stil ernsthaft versauen." - Steven Brust

Offline Talwyn

  • Hero
  • *****
  • Beiträge: 1.280
  • Geschlecht: Männlich
  • Username: Talwyn
Ich bin als Informatiker zwar gewöhnt bei der 0 mit dem Zählen zu beginnen. Aber angesichts der Tatsache, dass die DSL auch für Nicht-Programmierer intuitiv verwendbar sein soll schließe ich mich dem an.
Playing: D&D 5E
Hosting: Old School Essentials, Dungeon World
Reading: So tief die Schwere See, Mothership

Offline Skeeve

  • Hero
  • *****
  • Beiträge: 1.625
  • Geschlecht: Männlich
  • Username: XoxFox
... und ich schliesse mich da auch ganz Talwyn an.

Aber um noch was anderes interessantes anzusprechen: was soll die DSL eigentlich machen bz. was gehört alles zu einer DSL?

Dient die nur als Basis für solche Kalender oder sollen auch Funktionen rein wie "gehe vom aktuellen Datum einen Tick weiter und nennen mir den neuen Zeiitpunkt/das Datum" (falls der kleinstmögliche Schritt ein Tag ist, könnte man auch sagen "gehe einen Tag weiter und gibt mir das Datum"). Andere denkbare Funktionen: Fragen wie "welches Datum ist in einem Monat?".

Mir ist mittlerweile bewusst geworden, dass hier auf der Erde der Schritt auf "einen Tag später" oder "eine Woche später" zwar reichlich einfach und klar definiert ist. Aber welcher Termin ist bei "einen Monat später" gemeint wenn das aktuelle Datum z.b. der 31.Oktober ist? Bei "ein Jahr später" haben hier ja auch all diejenigen ein kleines Problem, die am 29. Feburar geboren wurden.

Ich weiß schon warum es bei meiner Welt dreizehn Monate mit jeweils 28 Tagen (4 Wochen a 7 Tage) gibt... Wenn ich mir schon eine eigene Welt ausdenke, dann kann ich da ja auch ein paar Sachen einfach halten.
... oft genug sind die Spieler die größten Feinde der Charaktere, da helfen auch keine ausgeglichenen Gegner

Hoher gesellschaftlicher Rang ist etwas, wonach die am meisten streben, die ihn am wenigsten verdienen.
Umgekehrt wird dieser Rang denen aufgedrängt, die ihn nicht wollen, aber am meisten verdienen. [Babylon 5]

Offline 1of3

  • Richtiges Mädchen!
  • Titan
  • *********
  • Proactive Scavenger
  • Beiträge: 18.991
  • Username: 1of3
    • Sea Cucumbers and RPGs
Ja, was kann passieren, wenn man vom 31. Oktober einen Monat weiter geht? Entweder 30. November, also den letztmöglichen Termin oder Bumm. Deshalb meinte ich oben schon, dass es da zwei Methoden geben sollte.

"later 1 month" liefert den 30. November, "+ 1 month" einen Fehler. Vorzugsweise einen Compilerfehler.

Offline Talwyn

  • Hero
  • *****
  • Beiträge: 1.280
  • Geschlecht: Männlich
  • Username: Talwyn
Ich denke man muss zwischen uniformen und nicht-uniformen Zeiteinheiten unterscheiden. Uniform wären z.B. Tage, Stunden Minuten und Sekunden. Für diese ist es einfach eine advanceBy Funktion zu definieren. Für alles andere muss man ein Verhalten definieren und dabei bleiben, da es nicht die eine sich aufdränge richtige Lösung gibt.


Gesendet von meinem SM-T813 mit Tapatalk

Playing: D&D 5E
Hosting: Old School Essentials, Dungeon World
Reading: So tief die Schwere See, Mothership

Offline 1of3

  • Richtiges Mädchen!
  • Titan
  • *********
  • Proactive Scavenger
  • Beiträge: 18.991
  • Username: 1of3
    • Sea Cucumbers and RPGs
OK. Fallen euch im Falle von nicht uniformen Einheiten Varianten ein, außer vom Übergang von einer kürzeren zu einer längeren Variante ggf. das letzte Datum auszugeben?

Offline Talwyn

  • Hero
  • *****
  • Beiträge: 1.280
  • Geschlecht: Männlich
  • Username: Talwyn
Für den Fall des Monats:

var d = Date(2016, 10, 31)
d advanceBy (1, MONTH)
println(d.format) // 30.11.2016

Für den Fall des Jahres

var d = Date(2016, 2, 29)
d.advanceBy (1, YEAR)
println(d.format) // 28.02.2017

Erscheint mir halbwegs intuitiv und leicht zu verstehen.
« Letzte Änderung: 24.10.2016 | 11:46 von Talwyn »
Playing: D&D 5E
Hosting: Old School Essentials, Dungeon World
Reading: So tief die Schwere See, Mothership

Offline 1of3

  • Richtiges Mädchen!
  • Titan
  • *********
  • Proactive Scavenger
  • Beiträge: 18.991
  • Username: 1of3
    • Sea Cucumbers and RPGs
Jo. Das ändert ein bisschen den Vorgang. Mit diesen Eigenschaften sind unsere Einheiten nicht mehr rein kompositionell. Es reicht nicht mehr zu wissen, welche Einheit in welcher anderen Einheit wie oft enthalten ist. Das Modell braucht Informationen über die einzelne Einheit.

Wenn wir das sowieso tun müssen, können wir uns die Umwandlung von den Builder-Objekten in den Kalender vereinfachen.

Ich schlage also vor: Wir machen eine Klasse TimeUnit (o.ä.) an der man all diese Dinge einstellen kann. Standardmäßig bieten wir Kandidaten wie Month, Week, Year, Hour, Minute, Second, Day an. Dann können User die entweder so benutzen, sie modifizieren ("Meine Monate sind alle gleich lang!") oder ganz neue bauen.

Das erspart uns beim parsen herauszufinden, was für eine Einheit das ist.

Offline Talwyn

  • Hero
  • *****
  • Beiträge: 1.280
  • Geschlecht: Männlich
  • Username: Talwyn
Klingt gut. Habe nur leider momentan wenig Zeit für das Projekt, da ich aus so einer Laune heraus einen Fernkurs zum Thema Machine Learning belegt habe und nun feststellen muss, dass das einen Sack voll Arbeit bedeutet.
Playing: D&D 5E
Hosting: Old School Essentials, Dungeon World
Reading: So tief die Schwere See, Mothership

Offline 1of3

  • Richtiges Mädchen!
  • Titan
  • *********
  • Proactive Scavenger
  • Beiträge: 18.991
  • Username: 1of3
    • Sea Cucumbers and RPGs
Frage an den Kundenkreis.

Wenn ich sagen möchte: Monat 5.

Welchen Operator, welches Zeichen, welches Wort würdet ihr dazwischen erwarten. Doppelpunkt scheidet aus. Also: "monat: 5" geht nicht als Eingabe. Was kann ich stattdessen nehmen, was einigermaßen einleuchtend scheint?

Offline achlys

  • Hero
  • *****
  • Beiträge: 1.732
  • Geschlecht: Männlich
  • Username: achlys
    • Tipareth's Net
Ein "=" "Monat=5", wobei ich der Klarheit wegen in der Ausgabe "Monatsnummer = 5" nehmen würde, oder man dreht es um: "5. Monat"

Gesendet mit nem Smartphone. Tippfehler dürfen behalten werden.


Offline 1of3

  • Richtiges Mädchen!
  • Titan
  • *********
  • Proactive Scavenger
  • Beiträge: 18.991
  • Username: 1of3
    • Sea Cucumbers and RPGs
OK. Gleichheitszeichen geht auch nicht. Da hab ich nicht dran gedacht.


Gesendet von meinem K011 mit Tapatalk


Offline sir_paul

  • Muffin-Fanboy
  • Legend
  • *******
  • Beiträge: 4.750
  • Geschlecht: Männlich
  • Username: sir_paul
Wie wäre es mit "ist" als Operator oder halt auf englisch "is". Dann könnte man einfach schreiben Tag ist 3,  Monat ist 5, etc.


Gesendet von meinem Nexus 9 mit Tapatalk


Offline achlys

  • Hero
  • *****
  • Beiträge: 1.732
  • Geschlecht: Männlich
  • Username: achlys
    • Tipareth's Net
Ein Pfeil? '->'

Offline 1of3

  • Richtiges Mädchen!
  • Titan
  • *********
  • Proactive Scavenger
  • Beiträge: 18.991
  • Username: 1of3
    • Sea Cucumbers and RPGs
Pfeil wäre möglich. Produziert grundsätzlich Tupel, also A->B ist das selbe wie (A,B), aber da könnte man mit implicits zaubern.

Nächste Frage. Aktuell sieht unser westlicher Kalender (wenn wir Schaltsekunden ignorieren) so aus:

Zitat

// Zeiteinheiten

 var millis = Tick("millisecond") countFromZero
  var seconds = Cycle from millis(1000) named "second" countFromZero
  var minutes = Cycle from seconds(60) named "minute" countFromZero
  var hours = Cycle from minutes(60) named "hour" countFromZero
  var days= Cycle from hours(24) named "day"
  var months= IrregularUnit("month")
  var years = IrregularUnit("year")

// Ausprägungen unregelmäßiger Zeiteinheiten

val january  = days(31) as month named "January" aka ("short","Jan")
  val february = days(28) as month named "February" aka ("short","Feb")
  val march = days(31) as month named "March" aka ("short","Mar")
  val april  = days(30) as month named "April" aka ("short","Apr")
  val may = days(31) as month named "May" aka ("short","May")
  val june  = days(30) as month named "June" aka ("short","Jun")
  val july = days(31) as month named "July" aka ("short","Jul")
  val august = days(31) as month named "August" aka ("short","Aug")
  val september  = days(30) as month named "September" aka ("short","Sep")
  val october  = days(31) as month named "October" aka ("short","Oct")
  val november = days(30) as month named "November" aka ("short","Nov")
  val december  = days(31) as month named "December" aka ("short","Dec")


  val standardYear = january + february + march + april + may + june + july +
                       august + september + october + november + december as year

  val leapFebruary = day(29) as month named "February"aka ("short","Feb")

  val leapYear = january + leapFebruary + march + april + may + june + july +
                   august + september + october + november + december as year

  val october1582 = october exclude (5 to 14)

  val year1582 = january + february + march + april + may + june + july +
    august + september + october1582 + november + december as year

// Wir bauen uns die Schaltregel und den Wechsel von Julianisch auf Gregorianisch

  val westernEra =
    Era given 1582 have year1582                                                    // wenn eine Jahreszahl abgefragt wird, wir das 1. Passende geliefert
      given  (x => x > 1582 && x%100==0 && x%400!=0) have standardYear
      given (_ % 4 == 0) have leapYear
      rest standardYear 
      exclude 0 // Jahr 0 gibts nicht.
      setEraNames ( x =>  Map( "default" ->  (if (x>=0) x + " CE" else x + " BCE") ) )  // setzt die Jahresnamen

// Wir machen einen Kalender draus

val westernCalendar = Calendar(westernEra)


Zur Verschönerung könnte man noch Funktionen wie divisbleBy und notDivisibleBy definieren. Teilbarkeit ist für Schaltregeln ein ziemlich übliches Kriterium.


Was jetzt noch fehlt, sind sekundäre Zyklen. Also bei uns z.B. die Wochen.

Um solche sekundären Zyklen anzuhängen, braucht es drei Informationen:
a) Welchen Zyklus
b) Wo soll ich ihn anhängen, also irgendeinen Zeitpunkt in der primären Ära.
c) Welchen Zeitpunkt des sekundären Zyklus möchte ich genau synchronisieren.

Bis auf (a) sollen die beiden anderen Angaben optional sein. Das ist soweit kein Problem. Fehlt (b) gehen wir von Datum 0 aus. Fehlt (c), nehmen wir an, der Anfang des sekundären Zyklus ist gemeint.

Offline TKarn

  • Experienced
  • ***
  • Beiträge: 230
  • Geschlecht: Männlich
  • Username: TKarn
Ich bin gerade auf das Thema gestossen. Wie weit ist das Projekt denn fortgeschritten? Gibt es das Programm irgendwo?
Da wollen wir mal sehen, was passiert!

Offline 1of3

  • Richtiges Mädchen!
  • Titan
  • *********
  • Proactive Scavenger
  • Beiträge: 18.991
  • Username: 1of3
    • Sea Cucumbers and RPGs
Hallo TKarn,

ich hatte deine Anfrage nicht gesehen, entschuldige bitte.

Du findest auf http://github.com/holothuroid/covella Quellcode. Der hat allerdings momentan noch einen Fehler. Für Zeiten vor 0 macht es Probleme, wenn man Schaltjahre benutzt.

Ich hab es letzte Woche eine Implementation hinbekommen, die regelmäßige Schaltjahre (alle 4 Jahre oder so) auch für Werte unter 0 hinbekommt. Ich würde noch einige Sachen ergänzen, korrigieren, refaktorieren und testen, bevor ich die nächste Version veröffentliche.

Es sei denn natürlich, du oder jemand anders möchte sich mit dem Quellcode auseinandersetzen. Dann kann ichs jederzeit hochschieben.
« Letzte Änderung: 9.05.2018 | 15:03 von 1of3 »

Offline TKarn

  • Experienced
  • ***
  • Beiträge: 230
  • Geschlecht: Männlich
  • Username: TKarn
Vielen Dank für die Antwort. Ich schaue es mir mal an.
Da wollen wir mal sehen, was passiert!

Offline 1of3

  • Richtiges Mädchen!
  • Titan
  • *********
  • Proactive Scavenger
  • Beiträge: 18.991
  • Username: 1of3
    • Sea Cucumbers and RPGs
So. Habe nun die aktuelle Version hochgeladen.

- Die Syntax für die häufigsten Problemstellungen ist mit Convenience-Funktion year() deutlich einfacher.

- Calendar optimiert automatisch, wenn man eine Schaltregel mit den eingebauten Funktionen (divisibleBy, notDivisibleBy, congruent) definiert. Oder eigens kreierten, die PeriodicFunction implementieren. Das habe ich für Symmetry454 mal vorgemacht. Der Kalender wird dadurch um Größenordnungen performanter.

- Intern habe ich den Code so umstrukturiert, dass so ziemlich alle optionalen Features wie (Namen, Zählung nicht ab 0 o.ä.) über Decorators laufen. Das hält die Komposit-Klassen klein. Ein paar einfache Unit-Tests habe ich auch hinzugefügt.

Was noch fehlt, ist eine spezielle Meta-Ära, um Wechsel im Kalendersystem effizient zu behandeln.

Offline TKarn

  • Experienced
  • ***
  • Beiträge: 230
  • Geschlecht: Männlich
  • Username: TKarn
Das läuft dann alles unter SCALA? Was muss ich sonst noch alles installieren?
Da wollen wir mal sehen, was passiert!