haskell error-handling

haskell - ¿Qué enfoque para el manejo de errores usar con tuberías(-core)?



error-handling (1)

Actualmente estoy escribiendo algunas tuberías-core / attoparsec plomería para un pequeño proyecto mío. Quiero que cada analizador proporcione un ByteString que aguarda la entrada de ByteString al analizador y arroja cualquier valor analizado (reiniciando el analizador). Sin manejo de errores, tendría un tipo como

parserP :: Monad m => Parser a -> Pipe ByteString a m r

Ahora, no estoy seguro de qué hacer con los errores de análisis. Mis ideas actuales son:

  • para agregar los errores al tipo de devolución (es decir, devolver un valor en Either ParseError r lugar de simplemente r )
  • requiere que la mónada proporcione el mecanismo de manejo de errores (es decir, que se requiera la mónada de la que se toma el tubo para implementar MonadError )
  • forzar a la mónada para que suministre un mecanismo de error tomando el Pipe over ErrorT ema para cualquier mónada m
  • agregar parámetros, permitiendo al usuario especificar el comportamiento (algo así como (ParseError -> P.Pipe ByteString amr) , y simplemente enlazar a la tubería así proporcionada en caso de un error de análisis)

La primera solución parece incorrecta, ya que usar el tipo de retorno de una tubería para el manejo de errores parece más bien un truco. Esto, por un lado, hace que la composición con el tubo sea más fea y parece estar más o menos incluida en la solución final (aparte de posiblemente perder la capacidad de permitir que un tubo aguas abajo pueda recuperarse de un error usando tryAwait y dejar de esperar valores ?).

La segunda solución parece equivocada, aunque no puedo entender por qué. Posiblemente porque (¿podría?) También requiere tomar un parámetro para traducir el ParseError al tipo de error que tenga la mónada (a menos que MonadError ParseError que la mónada implemente MonadError ParseError , lo que parece que resultaría en una gran cantidad de libros). Finalmente, parece que no recuerdo haber visto MonadError en todo eso, lo que sugeriría que hay algún problema con su uso.

La tercera solución funcionaría en mi caso, ya que la tubería formará parte de una tubería con una mónada (IO) especificada por el usuario que no debería preocuparse por los errores de análisis (analizará los datos de la red en un formato cedido a un usuario tipo especificado). Pero no parece tan elegante y, una vez más, ¿(posiblemente?) Resultaría en una gran cantidad de libros de contabilidad una vez utilizada en cualquier otro contexto.

Realmente no he pensado en la solución final, pero parece algo complicada.

Agradecería cualquier comentario sobre este caso en particular (no me sorprendería en absoluto si estoy lejos y me falta algo obvio), y para cualquier referencia (más o menos relevante) a las discusiones sobre el manejo de errores en las tuberías ( -core) / conductos / interatee, etc.

EDITAR: Otra posibilidad podría ser tomar solo una acción monádica (en lugar de una tubería completa), aunque no estoy muy seguro de si podría generalizar, especializarse o incluso ser equivalente al cuarto.


Si puedo, creo que puedo ayudar a organizar las ideas de todos sobre esto al describir la elección como tal. Usted:

  1. Layer Pipe dentro de un EitherT / ErrorT :

    EitherT e (Pipe abm) r

  2. Layer Pipe fuera de EitherT / ErrorT :

    Pipe ab (EitherT em) r

Desea el enfoque anterior, que también tiene la buena propiedad de que puede convertirlo en una instancia de MonadError (si eso es lo suyo).

Para entender la diferencia entre los dos enfoques, el segundo arroja errores al nivel de toda la tubería. El primero permite el manejo de errores en la granularidad de las tuberías individuales y maneja correctamente las tuberías compuestas.

Ahora para un poco de código. Usaré EitherT si no te importa, ya que estoy más cómodo con eso:

import Control.Error import Control.Pipe type PipeE e a b m r = EitherT e (Pipe a b m) r runPipeE = runPipe . runEitherT p1 <?< p2 = EitherT (runEitherT p1 <+< runEitherT p2)

Entonces simplemente use catchT y throwT dentro de una PipeE al contenido de su corazón.

Este enfoque tiene otra ventaja, que es que puedes aplicarlo de manera selectiva a ciertos segmentos de la tubería, pero luego eres responsable de manejar el valor excepcional potencial antes de componerlo con otras tuberías. Puede usar esa flexibilidad para usar valores excepcionales de diferentes tipos para diferentes etapas de la canalización o para no usarla en absoluto para etapas que no pueden fallar y evitar la sobrecarga de la comprobación de errores en esas etapas.