Les exemples de code suivants fournissent un contexte à ma question.
La classe Room est initialisée avec un délégué. Dans la première implémentation de la classe Room, il n'y a pas de garde contre les délégués qui lèvent des exceptions. Ces exceptions remonteront jusqu'à la propriété North, où le délégué est évalué (remarque: la méthode Main () montre comment une instance de Room est utilisée dans le code client):
public sealed class Room
{
private readonly Func<Room> north;
public Room(Func<Room> north)
{
this.north = north;
}
public Room North
{
get
{
return this.north();
}
}
public static void Main(string[] args)
{
Func<Room> evilDelegate = () => { throw new Exception(); };
var kitchen = new Room(north: evilDelegate);
var room = kitchen.North; //<----this will throw
}
}
Étant donné que je préfère échouer lors de la création d'objet plutôt que lors de la lecture de la propriété North, je change le constructeur en privé et j'introduis une méthode d'usine statique nommée Create (). Cette méthode intercepte l'exception levée par le délégué et lève une exception wrapper, avec un message d'exception significatif:
public sealed class Room
{
private readonly Func<Room> north;
private Room(Func<Room> north)
{
this.north = north;
}
public Room North
{
get
{
return this.north();
}
}
public static Room Create(Func<Room> north)
{
try
{
north?.Invoke();
}
catch (Exception e)
{
throw new Exception(
message: "Initialized with an evil delegate!", innerException: e);
}
return new Room(north);
}
public static void Main(string[] args)
{
Func<Room> evilDelegate = () => { throw new Exception(); };
var kitchen = Room.Create(north: evilDelegate); //<----this will throw
var room = kitchen.North;
}
}
Le bloc try-catch rend-il la méthode Create () impure?
la source
Create
est également impur, car il l'appelle.Create
fonction ne vous protège pas contre l'obtention d'une exception lors de l'obtention de la propriété. Si votre délégué lance, dans la vraie vie, il est très probable qu'il ne sera lancé que sous certaines conditions. Les chances sont que les conditions de lancer ne sont pas présentes pendant la construction, mais elles sont présentes lors de l'obtention de la propriété.Réponses:
Oui. C'est effectivement une fonction impure. Cela crée un effet secondaire: l'exécution du programme se poursuit ailleurs que dans l'endroit où la fonction devrait retourner.
Pour en faire une fonction pure,
return
un objet réel qui encapsule la valeur attendue de la fonction et une valeur indiquant une condition d'erreur possible, comme unMaybe
objet ou un objet Unité de travail.la source
Eh bien, oui ... et non.
Une fonction pure doit avoir une transparence référentielle - c'est-à-dire que vous devriez pouvoir remplacer tout appel possible à une fonction pure par la valeur renvoyée, sans changer le comportement du programme. * Votre fonction est garantie de toujours lancer pour certains arguments, il n'y a donc pas de valeur de retour pour remplacer l'appel de fonction, alors à la place, ignorons- le. Considérez ce code:
Si
func
est pur, un optimiseur pourrait décider defunc(arg)
le remplacer par son résultat. Il ne sait pas encore quel est le résultat, mais il peut dire qu'il n'est pas utilisé - il peut donc déduire que cette déclaration n'a aucun effet et la supprimer.Mais s'il
func(arg)
arrive à lancer, cette déclaration fait quelque chose - elle lève une exception! L'optimiseur ne peut donc pas le supprimer - peu importe que la fonction soit appelée ou non.Mais...
En pratique, cela importe peu. Une exception - au moins en C # - est quelque chose d'exceptionnel. Vous n'êtes pas censé l'utiliser dans le cadre de votre flux de contrôle régulier - vous êtes censé essayer de l'attraper, et si vous attrapez quelque chose, gérez l'erreur pour revenir à ce que vous faisiez ou pour l'accomplir d'une manière ou d'une autre. Si votre programme ne fonctionne pas correctement car un code qui aurait échoué a été optimisé, vous utilisez des exceptions incorrectes (sauf si c'est du code de test, et lorsque vous générez pour les tests, les exceptions ne doivent pas être optimisées).
Cela étant dit...
Ne lancez pas d'exceptions de fonctions pures avec l'intention de les intercepter - il y a une bonne raison que les langages fonctionnels préfèrent utiliser des monades plutôt que des exceptions de déroulement de pile.
Si C # avait une
Error
classe comme Java (et de nombreux autres langages), j'aurais suggéré de lancer unError
au lieu d'unException
. Cela indique que l'utilisateur de la fonction a fait quelque chose de mal (a passé une fonction qui lève), et de telles choses sont autorisées dans les fonctions pures. Mais C # n'a pas deError
classe, et les exceptions d'erreur d'utilisation semblent dériver deException
. Ma suggestion est de lancer unArgumentException
, indiquant clairement que la fonction a été appelée avec un mauvais argument.* Par calcul. Une fonction de Fibonacci implémentée à l'aide d'une récursivité naïve prendra beaucoup de temps pour les grands nombres et peut épuiser les ressources de la machine, mais celles-ci, car avec un temps et une mémoire illimités, la fonction retournera toujours la même valeur et n'aura pas d'effets secondaires (autres que l'allocation) mémoire et la modification de cette mémoire) - il est toujours considéré comme pur.
la source
(value, exception) = func(arg)
et supprimer la possibilité de lever une exception (dans certains langages et pour certains types d'exceptions, vous devez en fait la répertorier avec la définition, la même chose que les arguments ou la valeur de retour). Maintenant, ce serait aussi pur qu'avant (à condition qu'il retourne toujours aka jette une exception pour le même argument) Lancer une exception n'est pas un effet secondaire, si vous utilisez cette interprétation.func
cette façon, le bloc que j'ai donné aurait pu être optimisé, car au lieu de dérouler la pile, l'exception levée aurait été encodéeignoreThis
, ce que nous ignorons. Contrairement aux exceptions qui déroulent la pile, les exceptions encodées dans le type de retour ne violent pas la pureté - et c'est pourquoi de nombreux langages fonctionnels préfèrent les utiliser.func(arg)
renverrait le tuple(<junk>, TheException())
, etignoreThis
contiendra donc le tuple(<junk>, TheException())
, mais puisque nous n'utilisons jamaisignoreThis
l'exception n'aura aucun effet sur quoi que ce soit.Une considération est que le bloc try - catch n'est pas le problème. (Basé sur mes commentaires à la question ci-dessus).
Le principal problème est que la propriété du Nord est un appel d' E / S .
À ce stade de l'exécution du code, le programme doit vérifier les E / S fournies par le code client. (Il ne serait pas pertinent que la contribution soit sous la forme d'un délégué, ou que la contribution ait déjà été officiellement transmise).
Une fois que vous perdez le contrôle de l'entrée, vous ne pouvez pas vous assurer que la fonction est pure. (Surtout si la fonction peut lancer).
Je ne comprends pas pourquoi vous ne voulez pas vérifier l'appel à déplacer la pièce [Vérifier]? Selon mon commentaire à la question:
Comme Bart van Ingen Schenau l'a dit plus haut,
En général, tout type de chargement différé reporte implicitement les erreurs jusqu'à ce point.
Je suggère d'utiliser une méthode de déplacement de pièce [Vérifier]. Cela vous permettrait de séparer les aspects d'E / S impures en un seul endroit.
Semblable à la réponse de Robert Harvey :
Il appartiendrait au rédacteur de code de déterminer comment gérer l'exception (possible) de l'entrée. Ensuite, la méthode peut renvoyer un objet Room, ou un objet Null Room, ou peut-être faire bouillir l'exception.
De ce point, cela dépend:
la source
Hmm ... Je ne me sentais pas bien à ce sujet. Je pense que ce que vous essayez de faire, c'est de vous assurer que les autres font bien leur travail, sans savoir ce qu'ils font.
Étant donné que la fonction est transmise par le consommateur, qui pourrait être écrite par vous ou par une autre personne.
Que se passe-t-il si la fonction de transmission n'est pas valide pour s'exécuter au moment de la création de l'objet Pièce?
D'après le code, je ne pouvais pas être sûr de ce que faisait le Nord.
Disons que si la fonction est de réserver la chambre et qu'elle a besoin du temps et de la période de réservation, vous obtiendrez une exception si vous n'avez pas les informations prêtes.
Et si c'est une fonction de longue durée? Vous ne voudrez pas bloquer votre programme lors de la création d'un objet Pièce, que se passe-t-il si vous devez créer un objet Pièce 1000?
Si vous étiez la personne qui écrira le consommateur qui consomme cette classe, vous vous assureriez que la fonction transmise est écrite correctement, n'est-ce pas?
S'il y a une autre personne qui écrit au consommateur, il / elle pourrait ne pas connaître la condition "spéciale" de créer un objet Pièce, ce qui pourrait provoquer une exécution et ils se gratteraient la tête en essayant de découvrir pourquoi.
Une autre chose est que ce n'est pas toujours une bonne chose de lever une nouvelle exception. La raison en est qu'elle ne contiendra pas les informations de l'exception d'origine. Vous savez que vous obtenez une erreur (un mesaage convivial pour une exception générique), mais vous ne savez pas quelle est l'erreur (référence nulle, index hors limite, diviser par zéro, etc.). Ces informations sont très importantes lorsque nous devons faire une enquête.
Vous ne devez gérer l'exception que lorsque vous savez quoi et comment la gérer.
Je pense que votre code d'origine est assez bon, la seule chose est que vous voudrez peut-être gérer l'exception sur le "Main" (ou n'importe quelle couche qui sait comment le gérer).
la source
Je ne serai pas d'accord avec les autres réponses et dirai non . Les exceptions ne rendent pas impure une fonction par ailleurs pure.
Toute utilisation d'exceptions pourrait être réécrite pour utiliser des vérifications explicites des résultats d'erreur. Les exceptions pourraient simplement être considérées comme du sucre syntaxique en plus de cela. Le sucre syntaxique pratique ne rend pas le code pur impur.
la source
f
etg
sont tous les deux des fonctions pures, alorsf(x) + g(x)
devrait avoir le même résultat quel que soit l'ordre que vous évaluezf(x)
etg(x)
. Mais sif(x)
jetteF
etg(x)
jetteG
, alors l'exception levée de cette expression seraitF
sif(x)
est évalué en premier etG
sig(x)
est évalué en premier - ce n'est pas ainsi que les fonctions pures devraient se comporter! Lorsque l'exception est codée dans le type de retour, ce résultat se terminera par quelque chose commeError(F) + Error(G)
indépendamment de l'ordre d'évaluation.