Pular para o conteúdo

Objetos: cópia, imutabilidade e serialização

Arcane.Objetos: clonar raso e fundo, congelar, igualdade e hash coerentes, e serialização polimórfica que não deixa o dado escolher o tipo.

Identidade e igualdade#

is entre instâncias é identidade — dois spawn são dois objetos. É o certo para entidade. Para objeto de valor há __eq__, ou Objetos.igual, que compara estrutura sem obrigar o blueprint a escrever nada.

dataforge
adopt Arcane.Objetos as O

blueprint Endereco(rua, numero):
    x := 0

a := spawn Endereco("Flores", 10)
b := spawn Endereco("Flores", 10)
assert a isnt b                 // identidade
assert O.igual(a, b)            // estrutura
assert O.hash(a) is O.hash(b)   // coerente com igual

Contrato entre __eq__ e __hash__#

Uma instância é chave de vault. Quem declara __eq__ precisa declarar __hash__ coerente — objetos iguais com hash diferente somem do vault.

dataforge
blueprint Cpf(numero):
    action __eq__(outro):
        yield self.numero is outro.numero
    action __hash__():
        yield self.numero

donos := {}
donos[spawn Cpf("123")] := "Ana"
assert donos[spawn Cpf("123")] is "Ana"
assert sorted([3, 1, 2]) is [1, 2, 3]

Cópia#

dataforge
adopt Arcane.Objetos as O

blueprint Carrinho:
    itens := []

c1 := spawn Carrinho()
c1.itens.append("caneta")

raso := O.clonar(c1)
fundo := O.clonar_fundo(c1)
fundo.itens.append("lápis")

assert raso.itens is c1.itens          // a mesma lista
assert len(c1.itens) is 1              // o fundo não mexeu no original

A cópia não roda setup — ela não nasce, é duplicada. __copy__, __deepcopy__ e __clone__ no blueprint assumem a cópia quando existem.

Congelar#

dataforge
adopt Arcane.Objetos as O

blueprint Config:
    porta := 80

c := O.congelar(spawn Config())
assert O.congelado(c)
monitor:
    c.porta := 81
    assert no
handle FrozenObjectError:
    out "passe um objeto congelado a outra thread sem medo" 

Serialização polimórfica, e segura#

para_vault grava $tipo e resolve ciclos com $id/$ref. de_vault exige a lista de tipos que pode reconstruir: um dado de fora com "$tipo": "Admin" não decide qual blueprint o programa constrói. É assim que desserialização vira execução de código alheio em linguagens que deixaram o dado escolher.

dataforge
adopt Arcane.Objetos as O

blueprint Item(nome, preco):
    x := 0
blueprint Pedido:
    itens := []
    invariant len(self.itens) smaller_eq 100

p := spawn Pedido()
p.itens.append(spawn Item("caneta", 3))
dado := O.para_vault(p)
assert dado["$tipo"] is "Pedido"

volta := O.de_vault(dado, [Pedido, Item])
assert volta.itens[0].nome is "caneta"

monitor:
    O.de_vault({"$tipo": "Admin", "poderes": "todos"}, [Pedido, Item])
    assert no
handle UnsafeDeserializationError:
    out "tipo fora da lista: recusado" 
  • só os campos públicos saem por padrão; {"privados": yes} inclui os outros, para persistência própria;
  • a reconstrução confere as invariantes — um dado que chega violando a regra do tipo é recusado ali;
  • static versao_do_esquema := 2 grava $versao, e on_deserialize numa metaclasse migra o vault antes de montar.