2012-03-30 12 views
7

he definido un tipo¿Es posible depurar la coincidencia de patrones en una función Haskell?

data Expr = 
    Const Double 
    | Add Expr Expr 
    | Sub Expr Expr 

y lo declaró como una instancia de clase de tipos Eq:

instance Eq Expr where 
    (Add (Const a1) (Const a2)) == Const b = a1+a2 == b 
    (Add (Const a1) (Const a2)) == (Add (Const b1) (Const b2)) = a1+a2 == b1 + b2 

Por supuesto, la evaluación de la expresión Sub (Const 1) (Const 1) == Const 0 se producirá un error. ¿Cómo puedo depurar en tiempo de ejecución el proceso de coincidencia de patrones para detectar que está fallando? Me gustaría ver cómo Haskell toma los argumentos de == y recorre los patrones. ¿Es posible?

+4

GHC puede detectar el problema en tiempo de compilación. Si compila con '-Wall', le advertirá sobre el patrón incompleto y le mostrará los casos que omitió. – hammar

+6

La opción precisa es '-fwarn-incomplete-patterns'. Consulte http://stackoverflow.com/questions/7883023/algorithm-for-type-checking-ml-like-pattern-matching para ver cómo funciona. Por cierto, en tu ejemplo, recomiendo escribir 'eval :: Expr -> Double' y luego' x == y = eval x == eval y', pero sigue siendo bastante no estándar. – sdcvvc

+0

Sé sobre las advertencias. Me gustaría saber, por ejemplo, por qué un patrón que creo debería coincidir, no. –

Respuesta

2

edición: proporcionar una verdadera respuesta a la pregunta ...

me parece la forma más fácil de ver lo que los patrones son coincidentes es agregar trace declaraciones, así:

import Debug.Trace 

instance Eq Expr where 
    (Add (Const a1) (Const a2)) == Const b = trace "Expr Eq pat 1" $ a1+a2 == b 
    (Add (Const a1) (Const a2)) == (Add (Const b1) (Const b2)) = trace "Expr Eq pat 2" $ a1+a2 == b1 + b2 
    -- catch any unmatched patterns 
    l == r = error $ "Expr Eq failed pattern match. \n l: " ++ show l ++ "\n r: " ++ show r 

Si Don Incluya una declaración final para capturar cualquier otro patrón que no se haya igualado, obtendrá una excepción de tiempo de ejecución, pero creo que es más útil para ver qué datos está obteniendo. Entonces, generalmente es simple ver por qué no coincide con los patrones anteriores.

Por supuesto que no desea dejar esto en el código de producción. Solo inserto rastros según sea necesario y luego los elimino cuando termine. También puede usar CPP para dejarlos fuera de las compilaciones de producción.

También quiero decir que creo que la coincidencia de patrones es la forma incorrecta de hacerlo. Terminará con una explosión combinatoria en el número de patrones, que rápidamente se vuelve inmanejable. Si quiere hacer una instancia de Float por ejemplo, Expr, necesitará varios constructores más primitivos.

En su lugar, presumiblemente tiene una función de intérprete interpret :: Expr -> Double, o al menos podría escribir una.A continuación, puede definir

instance Eq Expr where 
    l == r = interpret l == interpret r 

Por coincidencia de patrones, básicamente estás re-escritura de su función de interpretar en el Eq ejemplo. Si desea hacer una instancia de Ord, terminará por volver a escribir la función de interpretación una vez más.

+0

Trace es el camino a seguir. Evaluar al doble no funcionará, porque algunas instancias de 'Expr' pueden ser funciones. –

2

Si desea obtener algunos ejemplos sobre cómo la coincidencia puede fallar, puede echar un vistazo a QuickCheck. Hay un ejemplo en el manual (el tamaño de los datos de prueba) sobre la generación y prueba de tipos de datos recursivos que parece adaptarse perfectamente a sus necesidades.

Mientras que el indicador -Wall le da una lista de patrones no coincidentes, una ejecución de QuickCheck le proporciona ejemplos de datos de entrada que conducen a su proposición dada al fracaso. Por ejemplo, si escribo un generador para su Expr y doy entrada a quickCheck una proposición prop_expr_eq :: Expr -> Bool que comprueba si un Expr es igual a sí mismo, obtengo muy rápidamente Const 0.0 como primer ejemplo de entrada no coincidente.

import Test.QuickCheck 
import Control.Monad 

data Expr = 
    Const Double 
    | Add Expr Expr 
    | Sub Expr Expr 
    deriving (Show) 

instance Eq Expr where 
    (Add (Const a1) (Const a2)) == Const b = a1+a2 == b 
    (Add (Const a1) (Const a2)) == (Add (Const b1) (Const b2)) = a1+a2 == b1 + b2 

instance Arbitrary Expr where 
    arbitrary = sized expr' 
     where 
     expr' 0 = liftM Const arbitrary 
     expr' n | n > 0 = 
      let subexpr = expr' (n `div` 2) 
      in oneof [liftM Const arbitrary, 
         liftM2 Add subexpr subexpr, 
         liftM2 Sub subexpr subexpr] 

prop_expr_eq :: Expr -> Bool 
prop_expr_eq e = e == e 

Como se ve, se ejecuta la prueba da un contraejemplo para demostrar que la prueba de la igualdad es erróneo. Sé que esto puede ser un poco exagerado, pero la ventaja si escribes cosas buenas es que también obtienes pruebas unitarias para tu código que observan las propiedades arbitrarias, no solo la exhaustividad de coincidencia de patrones.

*Main> quickCheck prop_expr_eq 
*** Failed! Exception: 'test.hs:(11,5)-(12,81): Non-exhaustive patterns in function ==' (after 1 test): 
Const 0.0 

PD: Otra buena lectura de las pruebas unitarias con QuickCheck está en el libro gratis real world haskell.

1

Puede dividir su patrón complejo en patrones más simples y usar trace para ver qué está pasando. Algo como esto:

instance Eq Expr where 
    x1 == x2 | trace ("Top level: " ++ show (x, y1)) True, 
       Add x11 x12 <- x1, 
       trace ("First argument Add: " ++ show (x11, x12)) True, 
       Const a1 <- x11, 
       trace ("Matched first Const: " ++ show a1) True, 
       Const a2 <- x12, 
       trace ("Matched second Const: " ++ show a2) True, 
       Const b <- x2 
       trace ("Second argument Const: " ++ show b) True 
      = a1+a2 == b 

Es un poco desesperado, pero los tiempos desesperados requieren medidas desesperadas. :) A medida que te acostumbras a Haskell, rara vez, o nunca, necesitas hacer esto.

Cuestiones relacionadas