2
votes

Comment attraper une erreur dans du code haskell pur sans monade

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>


4 commentaires

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 de Monad pour cela, et écrivez un safeHead :: [a] -> Ok a . Réalisez alors que vous voulez vraiment que ce soit une Monade car être une Monade est vraiment utile. Utilisez ensuite Peut-ê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 utiliser Data.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.


3 Réponses :


7
votes

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.


4 commentaires

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



3
votes

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.


1 commentaires

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 :))



2
votes

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


12 commentaires

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 (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 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.