Por algumas semanas eu estive pensando sobre a relação entre objetos - não especialmente sobre os objetos do OOP. Por exemplo, em C ++ , estamos acostumados a representá-lo colocando em camadas ponteiros ou contêineres de ponteiros na estrutura que precisa de acesso ao outro objeto. Se um objeto A
precisa ter um acesso a B
, não é raro encontrar um B *pB
em A
.
Mas não sou mais um programador de C ++ , escrevo programas usando linguagens funcionais, e mais especialmente em Haskell , que é uma linguagem funcional pura. É possível usar ponteiros, referências ou esse tipo de coisa, mas me sinto estranho com isso, como "fazer da maneira não-Haskell".
Então eu pensei um pouco mais sobre todas essas coisas de relação e cheguei ao ponto:
“Por que representamos essa relação por camadas?
Eu li algumas pessoas que já pensaram sobre isso ( aqui ). Do meu ponto de vista, representar relações através de gráficos explícitos é muito melhor, pois nos permite focar no núcleo do nosso tipo e expressar relações posteriormente através de combinadores (um pouco como o SQL ).
Por essência, quero dizer que, quando definimos A
, esperamos definir do que A
é feito , e não do que depende . Por exemplo, em um jogo de vídeo, se temos um tipo Character
, ele é legítimo para falar sobre Trait
, Skill
ou esse tipo de coisa, mas é se falamos Weapon
ou Items
? Não tenho mais tanta certeza. Então:
data Character = {
chSkills :: [Skill]
, chTraits :: [Traits]
, chName :: String
, chWeapon :: IORef Weapon -- or STRef, or whatever
, chItems :: IORef [Item] -- ditto
}
parece realmente errado em termos de design para mim. Prefiro 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
Além disso, quando chega o dia de adicionar novos recursos, podemos apenas criar novos tipos, novos gráficos e links. No primeiro design, teríamos que quebrar o Character
tipo ou usar algum tipo de trabalho para estendê-lo.
O que você acha dessa ideia? O que você acha melhor para lidar com esse tipo de problema no Haskell , uma linguagem funcional pura?
Character
deve ser capaz de segurar armas. Quando você tem um mapa de Characters
para Weapons
e um personagem está ausente no mapa, isso significa que o personagem atualmente não possui uma arma ou que o personagem não pode conter armas? Além disso, você faria pesquisas desnecessárias porque não sabe a priori se a pessoa Character
pode segurar uma arma ou não.
canHoldWeapons :: Bool
bandeira ao registro do personagem , que informa imediatamente se ele pode portar armas e se o seu personagem não é no gráfico, você pode dizer que o personagem não está segurando armas no momento. Faça giveCharacterWeapon :: WeaponGraph -> Character -> Weapon -> WeaponGraph
apenas agir como id
se essa bandeira fosse False
para o personagem fornecido ou use Maybe
para representar falha.
-Wall
on GHC: data Foo = Foo { foo :: Int } | Bar { bar :: String }
. Em seguida, digite cheques para chamar foo (Bar "")
ou bar (Foo 0)
, mas ambos geram exceções. Se você estiver usando construtores de dados de estilo posicional, não haverá problema porque o compilador não gera os acessadores para você, mas você não obtém a conveniência dos tipos de registro para estruturas grandes e é necessário mais clichê para usar bibliotecas como lente.