2009-12-20 11 views
10

Haskell es un lenguaje funcional puro, lo que significa que las funciones de Haskell no tienen efectos secundarios. La E/S se implementa utilizando mónadas que representan fragmentos de cómputo de E/S.¿Es posible probar el valor de retorno de las funciones de E/S de Haskell?

¿Es posible probar el valor de retorno de las funciones de E/S de Haskell?

Digamos que tenemos un simple 'hola mundo' programa:

main :: IO() 
main = putStr "Hello world!" 

¿Es posible para mí para crear un instrumento de prueba que se pueden ejecutar main y comprobar que la mónada de E/S devuelve la correcta 'valor'? ¿O el hecho de que las mónadas se supone que son bloques opacos de computación me impide hacer esto?

Nota, no estoy tratando de comparar los valores de retorno de las acciones de E/S. Quiero comparar el valor de retorno de las funciones de E/S - la mónada de E/S en sí.

Dado que en Haskell E/S se devuelve en lugar de ejecutarse, esperaba examinar el trozo de computación de E/S devuelto por una función de E/S y ver si era correcto o no. Pensé que esto podría permitir que las funciones de E/S fueran probadas en unidades de una manera que no pueden hacerlo en los idiomas imperativos donde E/S es un efecto secundario.

+1

Las mónadas no son necesariamente bloques "opacos" de computación. Por ejemplo, las mónadas List y Maybe tienen un comportamiento visible a la aplicación; la mónada IO es la única (que yo conozco) específicamente diseñada para aislar la lógica del programa de parte de su comportamiento. –

+1

Control.Monad.ST también es bastante opaco. – ephemient

+0

No soy exactamente bueno con las cosas de Computational Theory. Pero, ¿no es esto equivalente al problema de la detención? Quiero decir, tendrías una función que devuelve un programa (la acción IO) y deseas escribir otro programa para analizarla de manera estática para verificar que sea correcta. ¿Es esa una forma válida de describir el problema? ¿Esto es decidible? –

Respuesta

8

La forma en que haría esto sería crear mi propia món IO que contuviera las acciones que quería modelar. Me gustaría ejecutar los cálculos monádicos que quiero comparar dentro de mi mónada y comparar los efectos que tenían.

Tomemos un ejemplo. Supongamos que quiero modelar cosas de impresión. Entonces puede modelar mi mónada IO así:

data IO a where 
    Return :: a -> IO a 
    Bind :: IO a -> (a -> IO b) -> IO b 
    PutChar :: Char -> IO() 

instance Monad IO where 
    return a = Return a 
    Return a >>= f = f a 
    Bind m k >>= f = Bind m (k >=> f) 
    PutChar c >>= f = Bind (PutChar c) f 

putChar c = PutChar c 

runIO :: IO a -> (a,String) 
runIO (Return a) = (a,"") 
runIO (Bind m f) = (b,s1++s2) 
    where (a,s1) = runIO m 
     (b,s2) = runIO (f a) 
runIO (PutChar c) = ((),[c]) 

Así es como me gustaría comparar los efectos:

compareIO :: IO a -> IO b -> Bool 
compareIO ioA ioB = outA == outB 
    where ioA = runIO ioA ioB 

hay cosas que este tipo de modelo no maneja. La entrada, por ejemplo, es engañosa. Pero espero que se ajuste a su uso. También debo mencionar que hay formas más inteligentes y eficientes de modelar los efectos de esta manera. He elegido esta manera particular porque creo que es la más fácil de entender.

Para obtener más información, puedo recomendar el artículo "La belleza de la bestia: una semántica funcional para el equipo incómodo" que se puede encontrar en this page junto con algunos otros documentos relevantes.

+1

Creo que esta es la respuesta que aborda la intención del OP. La frase "valor de retorno" es un poco confusa en este contexto, pero creo que ctford significa valor de retorno en el sentido del valor de retorno de una función como 'putStr'. Es decir, comparando las acciones de E/S en sí mismas, no sus resultados. – Dan

+0

@ Dan Sí, ese es el sentido que quise decir. Es difícil hablar claramente sobre IO funcional puro :) – ctford

0

Lamento decirte que no puedes hacer esto.

unsafePerformIO básicamente vamos a lograr esto. Pero preferiría que lo haga no úsela.

Foreign.unsafePerformIO :: IO a -> a 

:/

+0

¿por qué no? 'Test = main >> = \ d -> return $ _test_value_ d' ?? –

+0

Entonces, ¿no hay forma de usar inseguroPerformIO en mi código de prueba pero no en mi código de producción para probar el IO mediante programación? – ctford

+1

@ctford: lo que realmente sugeriría es hacer que su código de prueba funcione en la mónada IO. – yairchu

4

Dentro de la mónada IO puede probar los valores de retorno de las funciones IO. Probar valores de devolución fuera de la mónada IO no es seguro: esto significa que se puede hacer, pero solo con el riesgo de romper su programa. Solo para expertos

Vale la pena señalar que, en el ejemplo que muestran, el valor de main tiene tipo IO(), lo que significa "Soy una acción IO que, cuando se lleva a cabo, hace algo de I/O y luego devuelve un valor de tipo (). " Tipo () se pronuncia "unidad", y solo hay dos valores de este tipo: la tupla vacía (también escrita () y pronunciada "unidad") y "inferior", que es el nombre de Haskell para un cálculo que no termina o va de otra manera incorrecto.

Vale la pena señalar que las pruebas de valores de retorno de las funciones de IO dentro de la mónada IO es perfectamente fácil y normal, y que la forma idiomática de hacerlo es mediante el uso de la notación do.

+1

Tenía la esperanza de probar que la acción IO en sí era correcta, a diferencia de lo que devuelve la acción (que, como usted señala correctamente, no es nada en este caso). Esperaba distinguir entre una acción que escribe "¡Hola mundo!" a stdout y uno que escribe "Foo" a stdout. Me doy cuenta de que lo que quiero hacer es algo inusual y teórico. – ctford

+0

OK, no entendí. Su mejor opción puede ser el chequeo rápido monádico (ver respuesta separada). Si lo intenta, háganos saber cómo lo hace. También puedes probar y ver si puedes obtener algunos consejos de los dones, que hicieron muchas pruebas interesantes con xmonad. –

0

Me gusta this answer a una pregunta similar en SO y los comentarios a la misma. Básicamente, IO normalmente producirá algún cambio que puede notarse desde el mundo exterior; su prueba tendrá que ver con si ese cambio parece correcto. (Por ejemplo, se produjo la estructura de directorios correcta, etc.)

Básicamente, esto significa 'pruebas de comportamiento', que en casos complejos puede ser bastante doloroso. Esta es parte de la razón por la cual debe mantener al mínimo la parte específica de IO de su código y mover la mayor parte de la lógica posible a funciones puras (por lo tanto, fácilmente comprobables).

Por otra parte, se puede utilizar una función de aserción:

actual_assert :: String -> Bool -> IO() 
actual_assert _ True = return() 
actual_assert msg False = error $ "failed assertion: " ++ msg 

faux_assert :: String -> Bool -> IO() 
faux_assert _ _ = return() 

assert = if debug_on then actual_assert else faux_assert 

(Es posible que desee definir debug_on en un módulo separado construido justo antes de la construcción por un script de construcción Además, esto es muy probable que sea. proporcionada en una forma más pulida por un paquete en Hackage, si no una biblioteca estándar ... Si alguien sabe de una herramienta de este tipo, por favor editar este post/comentario para que pueda editar.)

yo creo GHC sea ​​lo suficientemente inteligente como para omitir cualquier afirmación falsa que encuentre por completo, mientras que las afirmaciones reales definiran Tely bloquee su programa al fallar.

Esto es, IMO, muy poco probable que sea suficiente, igual tendrá que hacer pruebas de comportamiento en escenarios complejos, pero creo que podría ayudar a verificar que las suposiciones básicas que hace el código sean correctas.

1

Puede probar algunos código monádico con QuickCheck 2. Ha pasado mucho tiempo desde la última vez que leí el documento, por lo que no recuerdo si se aplica a las acciones de IO o a qué tipo de cálculos monádicos se puede aplicar. Además, es posible que le resulte difícil expresar las pruebas de su unidad como propiedades QuickCheck. Sin embargo, como usuario muy satisfecho de QuickCheck, diré que es lote mejor que no hacer nada o que hackear con unsafePerformIO.

+0

QuickCheck es genial. – ctford

Cuestiones relacionadas