La sémantique Haskell utilise une "valeur inférieure" pour analyser la signification du code Haskell. Ce n'est pas quelque chose que vous utilisez vraiment directement dans la programmation de Haskell, et le retour None
n'est pas du tout le même genre de chose.
La valeur inférieure est la valeur attribuée par la sémantique de Haskell à tout calcul qui ne parvient pas à évaluer normalement une valeur. Un tel moyen qu'un calcul Haskell peut faire est en fait de lever une exception! Donc, si vous essayez d'utiliser ce style en Python, vous devriez en fait simplement lever les exceptions comme d'habitude.
La sémantique de Haskell utilise la valeur inférieure car Haskell est paresseux; vous êtes capable de manipuler des "valeurs" qui sont retournées par des calculs qui n'ont pas encore été exécutés. Vous pouvez les passer à des fonctions, les coller dans des structures de données, etc. Un tel calcul non évalué peut lever une exception ou une boucle pour toujours, mais si nous n'avons jamais réellement besoin d'examiner la valeur, le calcul ne sera jamaisexécutez et rencontrez l'erreur, et notre programme global pourrait réussir à faire quelque chose de bien défini et à terminer. Donc, sans vouloir expliquer ce que signifie le code Haskell en spécifiant le comportement opérationnel exact du programme au moment de l'exécution, nous déclarons plutôt que de tels calculs erronés produisent la valeur inférieure et expliquons ce que cette valeur se comporte; fondamentalement, toute expression qui doit dépendre de toutes les propriétés de la valeur inférieure (autre qu'elle existe) entraînera également la valeur inférieure.
Pour rester «pur», toutes les façons possibles de générer la valeur inférieure doivent être traitées comme équivalentes. Cela inclut la "valeur inférieure" qui représente une boucle infinie. Puisqu'il n'y a aucun moyen de savoir que certaines boucles infinies sont en fait infinies (elles pourraient se terminer si vous les exécutez juste un peu plus longtemps), vous ne pouvez pas examiner aucune propriété d'une valeur inférieure. Vous ne pouvez pas tester si quelque chose est en bas, ne pouvez pas le comparer à autre chose, ne pouvez pas le convertir en chaîne, rien. Tout ce que vous pouvez faire avec un, c'est de le mettre en place (paramètres de fonction, partie d'une structure de données, etc.) intact et non examiné.
Python a déjà ce type de fond; c'est la "valeur" que vous obtenez d'une expression qui lève une exception ou ne se termine pas. Parce que Python est strict plutôt que paresseux, ces "fonds" ne peuvent pas être stockés n'importe où et potentiellement non examinés. Il n'est donc pas vraiment nécessaire d'utiliser le concept de la valeur inférieure pour expliquer comment les calculs qui ne renvoient pas de valeur peuvent toujours être traités comme s'ils avaient une valeur. Mais il n'y a également aucune raison de ne pas penser de cette façon aux exceptions si vous le vouliez.
Le fait de lever des exceptions est en fait considéré comme "pur". C'est intercepter des exceptions qui brise la pureté - précisément parce qu'il vous permet d'inspecter quelque chose sur certaines valeurs inférieures, au lieu de les traiter toutes de manière interchangeable. Dans Haskell, vous ne pouvez attraper que des exceptions dans le IO
qui permet une interface impure (donc cela se produit généralement sur une couche assez externe). Python n'applique pas la pureté, mais vous pouvez toujours décider par vous-même quelles fonctions font partie de votre "couche impure externe" plutôt que des fonctions pures, et ne vous autorisez qu'à y détecter des exceptions.
Le retour None
est complètement différent. None
est une valeur non inférieure; vous pouvez tester si quelque chose lui est égal et l'appelant de la fonction retournée None
continuera à s'exécuter, en utilisant peut-être de None
manière inappropriée.
Donc, si vous envisagez de lever une exception et que vous souhaitez «revenir en bas» pour imiter l'approche de Haskell, vous ne faites rien du tout. Laissez l'exception se propager. C'est exactement ce que les programmeurs Haskell veulent dire quand ils parlent d'une fonction renvoyant une valeur inférieure.
Mais ce n'est pas ce que les programmeurs fonctionnels veulent dire quand ils disent d'éviter les exceptions. Les programmeurs fonctionnels préfèrent les «fonctions totales». Ceux-ci renvoient toujours une valeur non inférieure valide de leur type de retour pour chaque entrée possible. Donc, toute fonction qui peut lever une exception n'est pas une fonction totale.
La raison pour laquelle nous aimons les fonctions totales est qu'elles sont beaucoup plus faciles à traiter comme des "boîtes noires" lorsque nous les combinons et les manipulons. Si j'ai une fonction totale renvoyant quelque chose de type A et une fonction totale qui accepte quelque chose de type A, alors je peux appeler la seconde à la sortie de la première, sans rien savoir de l'implémentation de l'une ou l'autre; Je sais que j'obtiendrai un résultat valide, peu importe la façon dont le code de l'une ou l'autre fonction sera mis à jour à l'avenir (tant que leur totalité est conservée et tant qu'ils conservent la même signature de type). Cette séparation des préoccupations peut être une aide extrêmement puissante pour la refactorisation.
Il est également quelque peu nécessaire pour des fonctions fiables d'ordre supérieur (fonctions qui manipulent d'autres fonctions). Si je veux écrire du code qui reçoit une fonction complètement arbitraire (avec une interface connue) comme paramètre, je dois le traiter comme une boîte noire parce que je n'ai aucun moyen de savoir quelles entrées peuvent déclencher une erreur. Si on me donne une fonction totale, aucune entrée ne provoquera une erreur. De même, l'appelant de ma fonction d'ordre supérieur ne saura pas exactement quels arguments j'utilise pour appeler la fonction qu'il me passe (sauf s'il veut dépendre de mes détails d'implémentation), donc passer une fonction totale signifie qu'il n'a pas à s'inquiéter ce que j'en fais.
Ainsi, un programmeur fonctionnel qui vous conseille d'éviter les exceptions préférerait que vous retourniez plutôt une valeur qui code l'erreur ou une valeur valide, et exige que pour l'utiliser, vous êtes prêt à gérer les deux possibilités. Des choses comme Either
types ou Maybe
/ Option
types sont quelques-unes des approches les plus simples pour le faire dans des langages plus fortement typés (généralement utilisés avec une syntaxe spéciale ou des fonctions d'ordre supérieur pour aider à coller ensemble les choses qui ont besoin d'une A
avec les choses qui produisent une Maybe<A>
).
Une fonction qui retourne None
(si une erreur s'est produite) ou une certaine valeur (s'il n'y a pas eu d'erreur) ne suit aucune des stratégies ci-dessus.
En Python avec typage canard, le style Soit / Peut-être n'est pas beaucoup utilisé, laissant plutôt des exceptions levées, avec des tests pour valider que le code fonctionne plutôt que de faire confiance aux fonctions pour être totales et automatiquement combinables en fonction de leurs types. Python n'a aucune facilité pour faire appliquer ce code utilise des choses comme les types peut-être correctement; même si vous l'utilisiez comme une question de discipline, vous avez besoin de tests pour exercer réellement votre code pour le valider. Ainsi, l'approche exception / bas est probablement plus adaptée à la programmation fonctionnelle pure en Python.