Durante algunas semanas he estado pensando en la relación entre los objetos, no especialmente los objetos de OOP. Por ejemplo, en C ++ , estamos acostumbrados a representar eso al agrupar punteros o contenedores de punteros en la estructura que necesita un acceso al otro objeto. Si un objeto A
tiene que tener un acceso a B
, no es raro encontrar una B *pB
en A
.
Pero ya no soy un programador de C ++ , escribo programas usando lenguajes funcionales, y más especialmente en Haskell , que es un lenguaje funcional puro. Es posible usar punteros, referencias o ese tipo de cosas, pero me siento extraño con eso, como "hacerlo de una manera que no sea de Haskell".
Luego pensé un poco más sobre todas esas cosas de relación y llegué al punto:
“¿Por qué representamos tal relación por capas?
Leí algunas personas que ya pensaron en eso ( aquí ). En mi punto de vista, representar las relaciones a través de gráficos explícitos es mucho mejor, ya que nos permite centrarnos en el núcleo de nuestro tipo y expresar relaciones más tarde a través de combinadores (un poco como lo hace SQL ).
Por núcleo quiero decir que cuando definimos A
, esperamos definir de qué A
está hecho , no de qué depende . Por ejemplo, en un juego de video, si tenemos un tipo Character
, es legítimo hablar de Trait
, Skill
o ese tipo de cosas, pero es que si hablamos de Weapon
o Items
? Ya no estoy tan seguro. Entonces:
data Character = {
chSkills :: [Skill]
, chTraits :: [Traits]
, chName :: String
, chWeapon :: IORef Weapon -- or STRef, or whatever
, chItems :: IORef [Item] -- ditto
}
me suena muy mal en términos de diseño. Prefiero algo como:
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
Además, cuando llega el día de agregar nuevas características, simplemente podemos crear nuevos tipos, nuevos gráficos y enlaces. En el primer diseño, tendríamos que romper el Character
tipo, o usar algún tipo de trabajo para extenderlo.
¿Qué opinas de esa idea? ¿Qué crees que es mejor tratar con ese tipo de problemas en Haskell , un lenguaje funcional puro?
Character
debería ser capaz de sostener armas. Cuando se tiene un mapa a partir Characters
de Weapons
y un carácter está ausente del mapa, ¿significa que el carácter actualmente no contener un arma, o que el personaje no puede tener armas en absoluto? Además, estaría haciendo búsquedas innecesarias porque no sabe a priori si Character
puede sostener un arma o no.
canHoldWeapons :: Bool
, que te permite saber de inmediato si puede sostener armas y luego si tu personaje no lo es? en el gráfico puedes decir que el personaje no tiene armas actualmente. Haga que giveCharacterWeapon :: WeaponGraph -> Character -> Weapon -> WeaponGraph
actúe como id
si esa bandera fuera False
para el personaje provisto, o use Maybe
para representar una falla.
-Wall
el GHC: data Foo = Foo { foo :: Int } | Bar { bar :: String }
. Luego escribe cheques para llamar foo (Bar "")
o bar (Foo 0)
, pero ambos generarán excepciones. Si está utilizando constructores de datos de estilo posicional, entonces no hay problema porque el compilador no genera los accesos por usted, pero no obtiene la conveniencia de los tipos de registro para estructuras grandes y se necesita más repetitivo para usar bibliotecas como lentes.