Question

En jouant avec D 2.0, j'ai trouvé le problème suivant:

Exemple 1:

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

Ceci compile et fonctionne comme prévu.

Lorsque j'essaie d'envelopper le tableau de chaînes dans une classe, je constate que cela ne fonctionne pas:

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

Ce code ne sera pas compilé car la fonction addMsg est impure. Je ne peux pas rendre cette fonction pure car elle modifie l'objet TestPure. Est-ce que je manque quelque chose? Ou est-ce une limitation?

Ce qui suit compile:

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

L'opérateur ~ = n'aurait-il pas été implémenté en tant que fonction impure du tableau msg? Comment se fait-il que le compilateur ne se plaint pas de cela dans la fonction run1?

Était-ce utile?

La solution

Depuis la v2.050, D a assoupli la définition de pure pour accepter le traitement dit "faiblement pur". fonctions aussi. Cela fait référence aux fonctions qui " ne lisent ou n'écrivent pas d'état mutable global " ;. Les fonctions faiblement pures ne sont pas identiques aux fonctions pures au sens fonctionnel du langage. La seule relation qui existe est qu’elles créent de véritables fonctions pures, a.k.a "fortement pures". fonctions capables d'appeler les faibles, comme l'exemple d'OP.

Avec cela, addMsg peut être marqué (faiblement) pur , puisque seule la variable locale this.msg est modifié:

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

et bien sûr, vous pouvez maintenant utiliser la fonction (fortement) pure run2 sans modification.

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

Autres conseils

D'autres ont déjà souligné qu'addMsg n'était pas pur et ne pouvait pas être pur car il mutait l'état de l'objet.

La seule façon de le rendre pur consiste à encapsuler les modifications que vous apportez. Pour ce faire, le moyen le plus simple consiste à effectuer une mutation, et il existe deux méthodes pour la mettre en œuvre.

Tout d'abord, vous pouvez le faire comme ceci:

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

Vous devez copier le tableau précédent, car dans une fonction pure, la référence this est en fait const. Notez que vous pouvez mieux faire la copie en allouant un nouveau tableau de la taille finale, puis en copiant les éléments en vous-même. Vous utiliseriez cette fonction comme suit:

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

De cette façon, la mutation est limitée à chaque fonction pure avec les modifications transmises via les valeurs renvoyées.

Une autre façon d’écrire TestPure serait de rendre les membres constants et d’effectuer toute la mutation avant de la transmettre au constructeur:

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

L’espoir que cela aide.

Veuillez revoir la définition des fonctions pures:

  

Les fonctions pures sont des fonctions qui produisent le même résultat pour les mêmes arguments. À cette fin, une fonction pure:

     
      
  • a des paramètres qui sont tous invariants ou qui sont implicitement convertibles en invariants
  •   
  • ne lit ni n'écrit aucun état mutable global
  •   

L’utilisation des fonctions pures a notamment pour effet de permettre leur parallélisation en toute sécurité. Toutefois, l’exécution de plusieurs instances de votre fonction en parallèle n’est pas sûre, car elles pourraient toutes deux modifier l’instance de classe simultanément, ce qui poserait un problème de synchronisation.

Je pense que votre code est conceptuellement correct. Cependant, vous avez peut-être trouvé un cas où l'analyse sémantique du compilateur n'est pas aussi bonne que celle de votre cerveau.

Considérons le cas où le source de la classe n'est pas disponible. Dans ce cas, le compilateur n'aurait aucun moyen de savoir que addMsg ne modifiait que la variable membre, de sorte qu'il ne pouvait pas vous permettre de l'appeler à partir d'une fonction pure.

Pour que cela soit possible dans votre cas, il devra disposer d’un traitement spécial pour ce type d’utilisation. Chaque règle de cas particulier ajoutée rend le langage plus compliqué (ou, s’il est laissé non documenté, le rend moins portable)

Juste un pressentiment, mais cette fonction ne donne pas toujours le même résultat.

Voir, elle renvoie une référence à un objet et, même si cet objet contient toujours les mêmes données, les objets renvoyés par plusieurs appels aux mêmes fonctions ne sont pas identiques. c'est-à-dire qu'ils n'ont pas la même adresse mémoire.

Lorsque vous renvoyez une référence à l'objet, vous renvoyez essentiellement une adresse mémoire, qui sera différente pour plusieurs appels.

Une autre façon de penser, une partie de la valeur de retour est l'adresse mémoire d'un objet, qui dépend d'un ou plusieurs états globaux, et si la sortie d'une fonction dépend d'un état global, alors ce n'est pas pur . Bon sang, ça ne doit même pas en dépendre; tant qu'une fonction lit un état global, elle n'est pas pure. En appelant "nouveau", vous lisez l'état global.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top