Domanda

Mentre giocavo con D 2.0 ho riscontrato il seguente problema:

Esempio 1:

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

Questo compila e funziona come previsto.

Quando provo a avvolgere l'array di stringhe in una classe trovo che non riesco a farlo funzionare:

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;
}

Questo codice non verrà compilato perché la funzione addMsg è impura. Non posso rendere pura questa funzione poiché altera l'oggetto TestPure. Mi sto perdendo qualcosa? O è una limitazione?

Compilare quanto segue:

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

L'operatore ~ ??= non sarebbe stato implementato come una funzione impura dell'array msg? Come mai il compilatore non si lamenta di ciò nella funzione run1?

È stato utile?

Soluzione

Dalla v2.050, D ha rilassato la definizione di pure per accettare il cosiddetto "debolmente puro" funziona anche. Questo si riferisce a funzioni che " non leggono o scrivono alcuno stato mutabile globale " ;. Le funzioni debolmente pure non sono non uguali come pure funzioni nel senso funzionale del linguaggio. L'unica relazione è che rendono reali le funzioni pure, a.k.a. "fortemente pure" funzioni in grado di chiamare quelle deboli, come nell'esempio di OP.

Con questo, addMsg può essere contrassegnato come (debolmente) pure , poiché solo la variabile locale this.msg è modificato:

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

e ovviamente ora puoi usare la funzione (fortemente) pura run2 senza alcuna modifica.

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

Altri suggerimenti

Altri hanno già sottolineato che addMsg non è puro e non può essere puro perché muta lo stato dell'oggetto.

L'unico modo per renderlo puro è incapsulare le modifiche che stai apportando. Il modo più semplice per farlo è tramite la mutazione del ritorno e ci sono due modi per implementarlo.

In primo luogo, potresti farlo in questo modo:

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

Devi copiare l'array precedente perché all'interno di una funzione pura, questo riferimento è in realtà const. Nota che potresti fare meglio la copia allocando un nuovo array della dimensione finale e quindi copiando gli elementi in te stesso. Utilizzeresti questa funzione in questo modo:

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

In questo modo, la mutazione è limitata a ciascuna funzione pura con le modifiche apportate tramite i valori di ritorno.

Un modo alternativo di scrivere TestPure sarebbe quello di rendere costanti i membri e fare tutta la mutazione prima di passarla al costruttore:

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);
    }
}

Spero che sia d'aiuto.

Si prega di rivedere la definizione di funzioni pure:

  

Le funzioni pure sono funzioni che producono lo stesso risultato per gli stessi argomenti. A tal fine, una funzione pura:

     
      
  • ha parametri che sono tutti invarianti o sono implicitamente convertibili in invarianti
  •   
  • non legge o scrive alcuno stato mutabile globale
  •   

Uno degli effetti dell'uso di funzioni pure è che possono essere parallelizzati in modo sicuro. Tuttavia, non è sicuro eseguire più istanze della tua funzione in parallelo, poiché entrambe potrebbero modificare contemporaneamente l'istanza della classe, causando un problema di sincronizzazione.

Penso che il tuo codice sia concettualmente corretto. Tuttavia potresti aver trovato un caso in cui l'analisi semantica del compilatore non è buona come quella del tuo cervello.

Considera il caso in cui l'origine della classe non è disponibile. In questi casi il compilatore non avrebbe modo di dire che addMsg modifica solo la variabile membro in modo da non poterti chiamare da una funzione pura.

Per consentirlo nel tuo caso, dovrebbe avere una gestione del caso speciale per questo tipo di utilizzo. Ogni regola di caso speciale aggiunta rende la lingua più complicata (o, se non documentata, la rende meno portabile)

Solo un sospetto, ma questa funzione non restituisce sempre lo stesso risultato.

Vedi, restituisce un riferimento ad alcuni oggetti, e mentre l'oggetto conterrà sempre gli stessi dati, gli oggetti restituiti da più chiamate alle stesse funzioni non sono identici; cioè, non hanno lo stesso indirizzo di memoria.

Quando si restituisce un riferimento all'oggetto, essenzialmente si restituisce un indirizzo di memoria, che sarà diverso tra più chiamate.

Un altro modo di pensarlo, parte del valore restituito è l'indirizzo di memoria di un oggetto, che dipende da alcuni stati globali, e se l'output di una funzione dipende dallo stato globale, allora non è puro . Inferno, non deve nemmeno dipendere da esso; fintanto che una funzione legge uno stato globale, non è pura. Chiamando "nuovo", stai leggendo lo stato globale.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top