Pregunta

Mientras jugaba con D 2.0 encontré el siguiente problema:

Ejemplo 1:

pure string[] run1()
{
   string[] msg;
   msg ~= "Test";
   msg ~= "this.";
   return msg;
}

Esto compila y funciona como se esperaba.

Cuando trato de ajustar la matriz de cadenas en una clase, encuentro que no puedo hacer que esto funcione:

class TestPure
{
    string[] msg;
    void addMsg( string s )
    {
       msg ~= s;
    }
};

pure TestPure run2()
{
   TestPure t = new TestPure();
   t.addMsg("Test");
   t.addMsg("this.");
   return t;
}

Este código no se compilará porque la función addMsg es impura. No puedo hacer que esa función sea pura ya que altera el objeto TestPure. ¿Me estoy perdiendo de algo? ¿O es esto una limitación?

Lo siguiente se compila:

pure TestPure run3()
{
    TestPure t = new TestPure();
    t.msg ~= "Test";
    t.msg ~= "this.";
    return t;
}

¿El operador ~ = no se implementó como una función impura de la matriz msg? ¿Cómo es que el compilador no se queja de eso en la función run1?

¿Fue útil?

Solución

Desde v2.050, D relajó la definición de pure para aceptar el llamado " débilmente puro " Funciona también. Esto se refiere a funciones que " no lee ni escribe ningún estado mutable global " ;. Las funciones débilmente puras no son lo mismo que las funciones puras en el sentido del lenguaje funcional. La única relación es que hacen funciones puras reales, a.k.a. '' fuertemente puro '' funciones capaces de llamar a los débiles, como el ejemplo de OP.

Con esto, addMsg puede ser marcado (débilmente) puro , ya que solo la variable local this.msg está alterado:

class TestPure
{
    string[] msg;
    pure void addMsg( string s )
    {
       msg ~= s;
    }
};

y, por supuesto, ahora puede usar la función (fuertemente) pure run2 sin modificación.

pure TestPure run2()
{
   TestPure t = new TestPure();
   t.addMsg("Test");
   t.addMsg("this.");
   return t;
}

Otros consejos

Otros ya han señalado que addMsg no es puro y no puede ser puro porque muta el estado del objeto.

La única forma de hacerlo puro es encapsular los cambios que está realizando. La forma más fácil de hacerlo es mediante la mutación de retorno, y hay dos formas de implementar esto.

En primer lugar, puede hacerlo así:

class TestPure
{
    string[] msg;
    pure TestPure addMsg(string s)
    {
        auto r = new TestPure;
        r.msg = this.msg.dup;
        r.msg ~= s;
        return r;
    }
}

Necesita copiar la matriz anterior porque dentro de una función pura, esta referencia es en realidad constante. Tenga en cuenta que podría hacer la copia mejor asignando una nueva matriz del tamaño final y luego copiando los elementos en usted mismo. Usaría esta función así:

pure TestPure run3()
{
    auto t = new TestPure;
    t = t.addMsg("Test");
    t = t.addMsg("this.");
    return t;
}

De esta manera, la mutación se limita a cada función pura con cambios que se pasan a través de valores de retorno.

Una forma alternativa de escribir TestPure sería hacer que los miembros const y hagan toda la mutación antes de pasarlo al constructor:

class TestPure
{
    const(string[]) msg;
    this()
    {
        msg = null;
    }
    this(const(string[]) msg)
    {
        this.msg = msg;
    }
    pure TestPure addMsg(string s)
    {
        return new TestPure(this.msg ~ s);
    }
}

Espero que eso ayude.

Revise la definición de funciones puras:

  

Las funciones puras son funciones que producen el mismo resultado para los mismos argumentos. Para ello, una función pura:

     
      
  • tiene parámetros que son todos invariantes o son implícitamente convertibles a invariantes
  •   
  • no lee ni escribe ningún estado mutable global
  •   

Uno de los efectos del uso de funciones puras es que pueden paralelizarse de forma segura. Sin embargo, no es seguro ejecutar varias instancias de su función en paralelo, ya que ambas podrían modificar la instancia de clase simultáneamente, causando un problema de sincronización.

Creo que su código es conceptualmente correcto. Sin embargo, es posible que haya encontrado casos en los que el análisis semántico del compilador no es tan bueno como el de su cerebro.

Considere el caso donde la fuente de la clase no está disponible. En esos casos, el compilador no tendría forma de decir que addMsg solo modifica la variable miembro, por lo que no puede permitirle llamarla desde una función pura.

Para permitirlo en su caso, tendría que tener un manejo de caso especial para este tipo de uso. Cada regla de caso especial agregada hace que el lenguaje sea más complicado (o, si no se documenta, lo hace menos portátil)

Solo una corazonada, pero esta función no siempre devuelve el mismo resultado.

Ver, devuelve una referencia a algún objeto, y aunque el objeto siempre contendrá los mismos datos, los objetos devueltos por varias llamadas a las mismas funciones no son idénticos; es decir, no tienen la misma dirección de memoria.

Cuando devuelve una referencia al objeto, esencialmente devuelve una dirección de memoria, que será diferente en varias llamadas.

Otra forma de pensarlo, parte del valor de retorno es la dirección de memoria de un objeto, que depende de algunos estados globales, y si la salida de una función depende del estado global, entonces no es pura . Demonios, ni siquiera tiene que depender de eso; mientras una función lea un estado global, entonces no es puro. Al llamar a "nuevo", está leyendo el estado global.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top