Alocação manual de memória e pureza

5

Linguagem como Haskell tem conceito de pureza . Na função pura, não posso mudar qualquer estado globalmente. De qualquer forma, Haskell abstrai totalmente o gerenciamento de memória, portanto, a alocação de memória não é um problema aqui.

Mas se as linguagens podem manipular memória diretamente como C ++, é muito ambíguo para mim. Nessas linguagens, a alocação de memória faz uma mutação visível. Mas se eu tratar de fazer um novo objeto como ação impura, na verdade, quase nada pode ser puro. Então, o conceito de pureza se torna quase inútil.

Como devo lidar com a pureza em linguagens que têm memória como objeto global visível?

    
por Eonil 09.04.2012 / 05:32
fonte

4 respostas

0

Eu acho que você está afirmando demais quando diz que "fazer novos objetos" sendo impuros implica que quase nada pode ser puro. Se você isolar sua alocação de memória de sua lógica principal, então a lógica da linha principal pode ser feita como "pura" ou "impura" conforme a situação exigir. Uma técnica que uso regularmente é definir funções de manipulação de dados que deixam o objeto this intacto, mas que usam um parâmetro para um destino no qual os resultados devem ser colocados. (Freqüentemente, eu permito que o destino seja o mesmo que o objeto this ). Por exemplo,

class Countdown
{
private:
    int _CountLeft;
public:
    void init(int CountLeft) { this->_CountLeft = CountLeft; }
    int getCountLeft() const { return this->_CountLeft; }
    bool decrease(Countdown &Dest) const {
        Dest._CountLeft = this->_CountLeft - 1;
        return Dest._CountLeft > 0;
    }
};

Estou ciente de que isso não é idiomático C ++ (mais sobre isso em um pouco), mas é uma maneira de implementar funções puras em C ++.

Agora, tentarei explicar alguns recursos estranhos da classe:

Por que usar um método init , em vez de um construtor?

Eu deliberadamente quis remover quaisquer preocupações de gerenciamento de memória da especificação deste objeto; usar um construtor ou destruidor introduz alguma dependência funcional do comportamento da classe na alocação / liberação de memória.

Como esta função é "pura"?

É "puro" no sentido de que, se sempre dermos uma nova instância do objeto como um parâmetro de destino, os dados de entrada para qualquer operação nunca serão alterados. Por exemplo:

Countdown CountdownArray[10];
CountdownArray[0].init(5);
for(
  int Index = 0;
  Index < 10 - 1 &&
    CountdownArray[Index].decrease(CountdownArray[Index+1];
  Index++)
{
    continue;
}

Bem, obviamente esse comportamento não é "puro", exatamente, mas os elementos de CountdownArray não foram modificados desde sua primeira inicialização; o método decrease faz comportar-se de maneira pura quando recebe um parâmetro de destino apropriado.

Como posso gerenciar a memória dessa classe com eficiência?

Neste caso, a classe é escrita de tal forma que o parâmetro Dest pode ser o mesmo objeto apontado por this :

Countdown CD;
CD.init(5);
while(CD.decrase(CD))
{
}

Isso obviamente torna o comportamento menos "puro", mas também torna o código gerado significativamente mais eficiente na memória.

Como esta técnica interage com a herança?

Boa pergunta. Eu evito herança ao usar esta técnica.

Conclusão

Um computador de propósito geral não pode ser praticamente "puro" - temos uma quantidade finita de recursos para trabalhar em um computador (memória, espaço em disco, registradores de CPU) e o uso eficiente desses recursos exige que eles sejam mutáveis. O que as pessoas querem dizer com "pureza" na computação é que isolamos o imutável do mutável, e o estado imutável pode ser considerado "puro". Você está certo de que a alocação de memória pode ter efeitos colaterais em outras partes do sistema (por exemplo, fazendo com que alocações posteriores falhem à medida que ficamos sem memória). Mas pode ser prático calcular a alocação de memória do código que queremos ser puro.

    
por 09.04.2012 / 06:50
fonte
1

Eu tenho que discordar da premissa básica.

C ++ (para usar o seu exemplo, embora o mesmo se aplique a C, Pascal, Ada, etc.) não oferece uma visibilidade real do heap. Você pode tentar alocar alguma memória, que pode ter êxito ou falhar - mas você não tem visibilidade sobre o motivo pelo qual uma alocação foi bem-sucedida ou falhou, nem quais outras alocações podem levar a esse sucesso / falha.

Em outras palavras, alocar alguma memória em um lugar não não tem um efeito que seja diretamente visível em qualquer outro lugar. Sim, é possível que uma alocação possa levar à falha de outra, mas 1) notar que o C ++ portátil pode colocá-los juntos, e 2) é quase impossível fazer essa conexão mesmo de uma maneira não portátil.

Na outra direção, nada em Haskell (ou qualquer outro idioma) pode mudar os fundamentos envolvidos de qualquer maneira. A tentativa de alocar mais memória do que a disponível (mesmo como espaço de endereço virtual) falhará, independentemente do idioma. Se o usuário executar outro programa que consuma toda a memória, nem o C ++ nem o Haskell (ou qualquer outra coisa) podem fazer muito a respeito, portanto, uma alocação que tenha sucesso em uma execução pode falhar em outra.

Na verdade, suponho que devo acrescentar que, embora não seja portável, muitos gerentes de heap (a maioria deles) incluem funções extras para percorrer o heap atual, ver quais blocos estão alocados etc. Sim, suponho que tal coisa poderia ser visto como quebrar a "pureza", mas meu palpite é que as pessoas, incluindo o "heap-walking" em seu software, provavelmente não se incomodam muito com isso (e aqueles que realmente se importam com a pureza simplesmente não usam essa capacidade.

    
por 09.04.2012 / 06:24
fonte
1

Pureza é um conceito que não se torna inútil apenas porque a linguagem de programação não o impõe. Haskell pode muito bem ser sua melhor aposta se você estiver procurando por garantia absoluta de pureza, mas isso não significa que o conceito seja inútil em C ++. Você certamente pode aumentar a pureza do código em C ++, e isso certamente pode ter um efeito positivo em sua qualidade.

    
por 09.05.2012 / 08:52
fonte
0

C ++ tem a capacidade de fornecer tais construções / formulários.

Você poderia implementar essas camadas de abstração, se você escolher.

Para reforçar isso, você pode começar adicionando const a tudo, usar os construtores de objetos para definir seu estado, introduzir ponteiros inteligentes, provavelmente perceber que ele é lento e criar alocadores locais de thread e assim por diante…

Aparentemente, o GHC também produzirá programas em C - talvez essa saída ajude a visualizar como ela pode ser abordada usando C ou C ++?

    
por 09.05.2012 / 10:14
fonte

Tags