Seit einigen Wochen denke ich über die Beziehung zwischen Objekten nach - nicht besonders über OOPs Objekte. In C ++ sind wir es beispielsweise gewohnt, dies darzustellen, indem Zeiger oder Container mit Zeigern in der Struktur geschichtet werden, die einen Zugriff auf das andere Objekt benötigen. Wenn ein Objekt A
Zugriff haben muss, B
ist es nicht ungewöhnlich, ein B *pB
In zu finden A
.
Aber ich bin kein C ++ - Programmierer mehr, ich schreibe Programme mit funktionalen Sprachen, insbesondere in Haskell , einer reinen funktionalen Sprache. Es ist möglich, Zeiger, Referenzen oder ähnliches zu verwenden, aber ich finde das seltsam, wie „es nicht nach Haskell zu machen“.
Dann dachte ich etwas tiefer über all diese Beziehungssachen nach und kam auf den Punkt:
„Warum repräsentieren wir eine solche Beziehung überhaupt durch Schichtung?
Ich habe einige Leute gelesen, die bereits darüber nachgedacht haben ( hier ). Aus meiner Sicht ist die Darstellung von Beziehungen durch explizite Diagramme viel besser, da wir uns auf den Kern unseres Typs konzentrieren und Beziehungen später durch Kombinatoren ausdrücken können (ähnlich wie bei SQL ).
Mit Kern meine ich, dass wir beim Definieren A
erwarten, zu definieren, woraus A
es besteht , und nicht, wovon es abhängt . Wenn wir zum Beispiel in einem Videospiel einen Typ haben Character
, ist es legitim, darüber zu sprechen Trait
, Skill
oder über solche Dinge, aber ist es, wenn wir über Weapon
oder sprechen Items
? Ich bin mir nicht mehr so sicher. Dann:
data Character = {
chSkills :: [Skill]
, chTraits :: [Traits]
, chName :: String
, chWeapon :: IORef Weapon -- or STRef, or whatever
, chItems :: IORef [Item] -- ditto
}
klingt für mich wirklich falsch im Design. Ich würde lieber etwas bevorzugen wie:
data Character = {
chSkills :: [Skill]
, chTraits :: [Traits]
, chName :: String
}
-- link our character to a Weapon using a Graph Character Weapon
-- link our character to Items using a Graph Character [Item] or that kind of stuff
Wenn ein Tag kommt, um neue Funktionen hinzuzufügen, können wir einfach neue Typen, neue Diagramme und Verknüpfungen erstellen. Im ersten Entwurf müssten wir den Character
Typ brechen oder irgendeine Art von Arbeit verwenden, um ihn zu erweitern.
Was denkst du über diese Idee? Was ist Ihrer Meinung nach am besten, um diese Art von Problemen in Haskell , einer rein funktionalen Sprache , zu lösen ?
Character
in der Lage sein sollte, Waffen zu halten. Wenn Sie eine Karte von Characters
bis haben Weapons
und ein Charakter nicht auf der Karte vorhanden ist, bedeutet dies, dass der Charakter derzeit keine Waffe besitzt oder dass der Charakter überhaupt keine Waffen halten kann? Außerdem würden Sie unnötige Suchvorgänge durchführen, da Sie nicht a priori wissen, ob a Character
eine Waffe halten kann oder nicht.
canHoldWeapons :: Bool
Flagge zu geben, die Sie sofort darüber informiert, ob er Waffen halten kann und ob Ihr Charakter dies nicht ist In der Grafik können Sie sagen, dass der Charakter derzeit keine Waffen hält. Machen Sie giveCharacterWeapon :: WeaponGraph -> Character -> Weapon -> WeaponGraph
einfach so, als id
ob dieses Flag False
für das angegebene Zeichen ist, oder verwenden Sie es Maybe
, um einen Fehler darzustellen.
-Wall
GHC keine Warnungen aus : data Foo = Foo { foo :: Int } | Bar { bar :: String }
. Anschließend werden Checks zum Aufrufen von foo (Bar "")
oder eingegeben bar (Foo 0)
, aber beide lösen Ausnahmen aus. Wenn Sie Positionsstil-Datenkonstruktoren verwenden, gibt es kein Problem, da der Compiler die Accessoren nicht für Sie generiert, Sie jedoch nicht die Bequemlichkeit der Datensatztypen für große Strukturen erhalten und die Verwendung von mehr Boilerplate erforderlich ist Bibliotheken wie Objektiv.