On l'a probablement demandé à mort, mais est-ce que quelqu'un connaîtrait le moyen le moins intrusif de "détecter" l'erreur
catch :: Exception e => IO a -> (e -> IO a) -> IO a
dans des calculs haskell
"purs"? p>
(disons, un code contenant head []
, pas sûr, que je ne veux pas rendre pur pour de vrai, ni forcer à être monadique)
PS : bien sûr, si vous pouvez et souhaitez concevoir à partir de zéro, faites cuire cela dans (avec un type peut-être). La question ici est que je veux sciemment vouloir le garder dangereux (pour conserver une traduction simple à partir d'une langue non sûre) et me demander quelle est la manière la plus agréable / la moins moche de le faire.
p>
3 Réponses :
Il n'y a aucun moyen d'utiliser catch
en code pur. C'est par conception: les exceptions sont gérées par le système IO. C'est pourquoi le type de catch
utilise IO
. Si vous souhaitez gérer l'échec en code pur, vous devez utiliser un type pour représenter la possibilité d'échec. Dans ce cas, l'échec est que la valeur peut parfois ne pas exister.
Le type que nous utilisons dans Haskell pour désigner une valeur qui peut exister ou non s'appelle Maybe
. Peut-être
est une instance de Monad
, donc c'est "monadique", mais cela ne devrait pas vous dissuader de l'utiliser pour son usage prévu. Donc, la fonction que vous voulez est headMay
du package safe : headMay :: [a] -> Peut-être un
.
Cela dit, si vous voulez éviter les monades, vous pouvez à la place utiliser une fonction qui décompresse la liste:
headDef :: a -> [a] -> a headDef def = listElim def const
Comme vous pouvez le voir, cela remplace un [] avec
nil
et un :
avec un appel à contre
. Maintenant, vous pouvez écrire une tête sûre qui vous permet de spécifier une valeur par défaut:
listElim :: b -> (a -> [a] -> b) -> [a] -> b listElim nil _ [] = nil listElim _ cons (x:xs) = cons x xs
Malheureusement, les fonctions sont une instance de Monad
, il s'avère donc que vous avez été "force [d] à être monadique" après tout! Il n'y a vraiment pas moyen d'échapper à la monade, alors il vaut peut-être mieux apprendre à les utiliser de manière productive.
Au lieu d'un listElim
personnalisé, il peut être utile de suggérer foldr
: headDef = foldr const
.
Je veux dire que j'aurais pu l'écrire avec une correspondance de motifs, mais c'était plus amusant.
Le problème est que les exceptions sont gérées dans IO
mais générées en code pur.
L'usage de monadique n'était peut-être pas assez clair: je ne veux pas séquencer mon code d'une manière particulière. Tout le code utilisant un peut-être n'est pas structuré comme une séquence d'étapes propageant une valeur optionnelle en enveloppant / dépliant, comme le fait CPS / monad
Si vous voulez vivre dangereusement, vous pouvez utiliser unsafePerformIO
:
(let a = a in a) `seq` error "hallo!"
Le problème est que ce n'est pas garanti du tout d'être bien comporté. Par exemple, si vous lui passez la valeur
catch'Pure' :: Exception e => a -> (e -> a) -> a catch'Pure' v h = unsafePerformIO $ evaluate v `catch` (pure . h)
, le compilateur a le droit de produire une boucle infinie parfois et un message d'erreur d'autres fois, violant l'attente fondamentale de pureté. Il y a des raisons d'utiliser du code qui ressemble à ceci, mais il faut faire très attention pour qu'il se comporte bien.
C'est bon, mon code (importé) n'est pas sûr pour commencer, et je suis d'accord avec ça. J'espère que la gestion des erreurs ne le rendra pas plus dangereux mais moins sûr :))
Control.Spoon code >
du package cuillère fournit un principalement -enveloppe sécurisée autour des opérations non sécurisées requises pour cela.
λ> spoon (head [1,2,3]) Just 1 λ> spoon (head []) Nothing
spoon
utilise une combinaison de unsafePerformIO
et deepseq
que je ne recommanderais certainement pas pour une utilisation sérieuse. Si vous voulez un head
qui renvoie un Maybe
, vous pouvez utiliser celui dans le package 'safe'.
@ReinHenrichs Je ne le recommanderais pas non plus, mais l'OP a dit "que je ne veux pas rendre pur pour de vrai, ni forcer à être monadique", et au moins cuillère code> (ou
cuillère à café
, si vous ne voulez pas le deepseq
) est plus sûr que d'écrire directement unsafePerformIO
.
En quoi le retour d'un Maybe via une magie non sûre est-il moins «monadique» que le simple retour d'un Maybe de la manière habituelle? S'ils sont d'accord avec Maybe, pourquoi ne pas simplement suggérer headMay?
En fait, n'est-il pas plus monadique puisqu'il utilise l'IO sous le capot?
Là encore, j'avoue que je ne sais souvent pas ce que les gens veulent dire quand ils disent «monadique» (ou, d'ailleurs, «pur»).
@ReinHenrichs J'imagine que l'OP a une sorte de calcul compliqué géant avec une tête
profondément enfouie à l'intérieur de couches et de couches de fonctions qui ont toutes été construites avec l'hypothèse que la tête code > ne recevrait jamais une liste vide - une hypothèse qui, pour une raison quelconque, a maintenant changé. La partie la plus difficile de résoudre ce problème de la bonne manière n'est pas de récupérer un
Peut-être
de la tête
- c'est facile - la partie la plus difficile est de réparer fastidieusement toutes les couches de fonctions pour sonder le Peut-être
jusqu'à l'appelant externe de manière monadique. Et donc, j'imagine, l'OP cherche un raccourci rapide et sale.
De toute évidence, c’est une terrible décision en matière d’ingénierie logicielle et elle reviendra probablement les mordre. Mais certains projets ponctuels ne nécessitent pas une bonne ingénierie logicielle. Ou du moins, c’est ce que je me dis quand j’essaie de ne pas être cynique sur tout le domaine de l’informatique…
C'est ... un peu exagéré? 😀
@ReinHenrichs Votre recommandation compare le code sûr et le code dangereux. le problème ici est "comment gérer le code non sécurisé", et nous ne devrions pas nous laisser tromper par le type pur de Haskell qui ne sont en réalité pas du tout pur ( head
étant juste un exemple)
@AndersKaseorg C'est exactement la situation. C'est bien que haskell favorise la pureté, mais il est également vrai que nous avons besoin de moyens pratiques pour gérer les mensonges de haskell à ce sujet. Le code importé jette une exception quelque part, je sais pourquoi, et je n'ai pas envie de réécrire tout ça juste pour le faire fonctionner, alors que cela pourrait être atteint en une seule étape.
@ReinHenrichs monadique dans ce contexte signifiait donner un séquencement explicite des opérations dans la syntaxe, ce qui est parfois nécessaire, et souvent non. J'imagine que c'est un terme surchargé, j'aurais dû le dire plus clairement.
@nicolas Ce n'était certainement pas clair pour moi que votre question voulait ce genre de réponse, mais je suis heureux que vous ayez obtenu de bonnes réponses.
Utilisez
Peut-être
. La détection des erreurs est monadique - vous ne pouvez pas «forcer [it] à être monadique». Il est monadique par ses propriétés.Écrivez vos propres données
Ok a = Non | Oui un
, n'écrivez pas une instance deMonad
pour cela, et écrivez unsafeHead :: [a] -> Ok a
. Réalisez alors que vous voulez vraiment que ce soit uneMonade
car être uneMonade
est vraiment utile. Utilisez ensuitePeut-être
à la place.Je ne suis pas sûr de bien comprendre votre question. Pouvez-vous être plus précis, avec un exemple de ce que vous voulez éviter? Si ce n'est que pour
head
, vous pouvez utiliserData.Maybe.listToMaybe
.Cela serait spécialisé dans Peut-être, m'obligerait à réécrire du code (qui dans certains cas vient de SML), et toute autre exception pourrait encore être levée. Je veux m'assurer qu'une valeur de type a est effectivement sans effet en gérant l'exception.