2011-02-25 17 views
20

En mi programa Haskell, quiero leer en un valor dado por el usuario que usa la función getLine. Entonces quiero usar la función read para convertir este valor de una cadena al tipo de Haskell apropiado. ¿Cómo puedo detectar los errores de análisis arrojados por la función read y solicitar al usuario que vuelva a ingresar el valor?¿Cómo atrapar una excepción no analizada de la función de lectura en Haskell?

¿Estoy en lo cierto al pensar que esto no es un "IO Error" porque no es un error causado por el sistema IO que no funciona correctamente? Es un error semántico, entonces no puedo usar los mecanismos de manejo de errores de IO?

+2

En el próximo GHC 7.6, habrá 'Text.Read.readEither :: Read a => String -> E String a' y' Text.Read.readMaybe :: Read a => String -> Maybe a'. – sdcvvc

Respuesta

26

No desea. Que desea utilizar reads lugar, posiblemente como esa:

maybeRead = fmap fst . listToMaybe . reads 

(aunque es posible que desee a error si el segundo elemento de la tupla no es "", es decir, si hay una cadena restante, también)

el razón razón por la que desea utilizar lee en lugar de la captura de error excepciones es que las excepciones en código puro son malas, porque es muy fácil de tratar de atraparlos en el lugar equivocado: Tenga en cuenta que sólo vuelan cuando están forzado, no antes. Localizar dónde está eso puede ser un ejercicio no trivial. Esa es (una de las razones) por la cual a los programadores de Haskell les gusta mantener su código total, es decir, sin terminación y sin excepciones.

Es posible que desee echar un vistazo a un marco de análisis adecuado (por ejemplo, parsec) y haskeline, también.

+8

'maybeRead' está en los paquetes' utility-ht' y 'applicative-extras' en hackage (entre otros), y se propone su inclusión en la base. No estoy seguro del estado de esa propuesta. – luqui

+1

Pruebe readMaybe desde Text.Read – TheEnvironmentalist

8

Esto es un apéndice de la respuesta de @barsoap más que cualquier otra cosa.

Las excepciones de Haskell pueden arrojarse a cualquier parte, incluso en código puro, pero solo pueden capturarse dentro de la mónada IO. Para detectar las excepciones arrojadas por el código puro, necesita usar un catch o try en la declaración IO que forzaría la evaluación del código puro.

str2Int :: String -> Int -- shortcut so I don't need to add type annotations everywhere 
str2Int = read 

main = do 
    print (str2Int "3") -- ok 
    -- print (str2Int "a") -- raises exception 
    eVal <- try (print (str2Int "a")) :: IO (Either SomeException()) 
    case eVal of 
    Left e -> do -- couldn't parse input, try again 
    Right n -> do -- could parse the number, go ahead 

Debe utilizar algo más específico que SomeException porque eso va a coger cualquier cosa. En el código anterior, el try devolverá un Left exception si read no puede analizar la cadena, pero también devolverá un Left exception si hay un error de IO al intentar imprimir el valor, o cualquier cantidad de otras cosas que posiblemente podrían salir mal (sin memoria, etc.)

Ahora, aquí está por qué las excepciones del código puro son malas. ¿Qué ocurre si el código IO no obliga realmente a evaluar el resultado?

main2 = do 
    inputStr <- getLine 
    let data = [0,1,read inputStr] :: [Int] 
    eVal <- try (print (head data)) :: IO (Either SomeException()) 
    case eVal of 
    Right() -> do -- No exception thrown, so the user entered a number ?! 
    Left e -> do -- got an exception, probably couldn't read user input 

Si ejecuta este, encontrará que siempre termina en la rama Right de la declaración del caso, no importa lo que el usuario ha introducido. Esto se debe a que la acción IO pasada a try no intenta nunca read la cadena ingresada. Imprime el primer valor de la lista data, que es constante y nunca toca el final de la lista. Por lo tanto, en la primera rama de la declaración del caso, el codificador cree que los datos se evalúan pero no es así, y read aún puede emitir una excepción.

read está destinado a la deserialización de datos, sin analizar la entrada ingresada por el usuario. Use reads o cambie a una biblioteca de combinación de analizador real.Me gusta uu-parsinglib, pero parsec, polyparse, y muchos otros también son buenos. Es muy probable que necesite la potencia adicional antes de que pase mucho tiempo.

3

Aquí hay una mejor maybeRead que permite solamente para servicios móviles espacios en blanco, pero nada más:

import Data.Maybe 
import Data.Char 

maybeRead2 :: Read a => String -> Maybe a 
maybeRead2 = fmap fst . listToMaybe . filter (null . dropWhile isSpace . snd) . reads 
+0

'null. dropWhile isSpace' es solo 'all isSpace', ¿no? –

7

Hay readMaybe y readEither que satisfagan sus expectativas. Encuentra esto funciones en el paquete Text.Read.

+0

La mejor respuesta en mi humilde opinión! – Titou

Cuestiones relacionadas