¿Por qué GHC se queja de patrones no exhaustivos?

Cuando compilo el siguiente código con GHC (usando el -Wall bandera):

module Main where

data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show)

insert :: (Ord a) => a -> Tree a -> Tree a
insert x EmptyTree = Node x EmptyTree EmptyTree
insert x (Node a left right)
    | x == a = Node a left right
    | x < a = Node a (insert x left) right
    | x > a = Node a left (insert x right)

main :: IO()
main = do
    let nums = [1..10]::[Int]
    print . foldr insert EmptyTree $ nums

GHC se queja de que la coincidencia de patrones en insert no es exhaustivo:

test.hs|6| 1:
||     Warning: Pattern match(es) are non-exhaustive
||              In an equation for `insert': Patterns not matched: _ (Node _ _ _)

¿Por qué GHC emite esta advertencia? Es bastante obvio que el patrón del que se queja GHC se maneja en insert x (Node a left right).

preguntado el 22 de mayo de 12 a las 10:05

3 Respuestas

Es porque la coincidencia de patrones está incompleta. No hay garantía de que uno de x==a, x<a o el x>a sostiene Por ejemplo, si el tipo es Double y x es NaN entonces ninguno de ellos es True.

contestado el 22 de mayo de 12 a las 13:05

Mala mía, tienes razón. Es por eso que odio profundamente esos estándares ieee sobre dobules :) - ricardo t

+1 porque no sabía que comparar con NaN siempre se evalúa como falso. - Alexandros

Interesante que esto signifique compare y <, ==, > son inconsistentes para los flotadores (ya que compare no puedes digamos que los 3 son False; tiene que declarar uno True en todos los casos o ser parcial; yo obtengo (sqrt (-1)) `compare` (1/0) == GT). Si tomas como una ley que ellos debemos sea ​​consistente, entonces, de hecho, el patrón del OP coincide is completo (GHC simplemente no lo prueba conociendo la ley), y es un error que flota o tiene una instancia de Ord (o un error que la instancia de Ord intenta seguir los estándares IEEE para compararlos con NaN). - paquet

El compilador de ghc no asume que las instancias sigan ninguna ley. Sería muy molesto si lo hiciera, ya que cuando se trabaja con DSL, a menudo desea crear instancias que infrinjan las leyes. - augusts

Riccardo tiene razón, GHC no infiere que sus guardias no puedan ser todas falsas. Así que acepta su respuesta por favor.

Voy a hacer una digresión y hablar sobre el estilo de codificación.

Su motivación para no consumir otherwise puede haber sido que se ve feo:

insert :: (Ord a) => a -> Tree a -> Tree a
insert x EmptyTree = Node x EmptyTree EmptyTree
insert x (Node a left right)
    | x == a    = Node a left right
    | x < a     = Node a (insert x left) right
    | otherwise = Node a left (insert x right)

Mirando este código, un lector humano debe confirmarse a sí mismo que la guardia final acepta precisamente aquellos casos en los que x > a.

En su lugar, podríamos escribirlo así:

insert :: (Ord a) => a -> Tree a -> Tree a
insert x EmptyTree = Node x EmptyTree EmptyTree
insert x (Node a left right) = case x `compare` a of
    EQ -> Node a left right
    LT -> Node a (insert x left) right
    GT -> Node a left (insert x right)

La Ordering tipo devuelto por compare solo tiene los tres valores EQ, LTy GT, para que GHC pueda confirmar que ha cubierto todas las posibilidades, y un lector humano puede ver fácilmente que las ha cubierto correctamente.

Este es también un código más eficiente: llamamos compare una vez, en lugar de llamar == y luego probablemente llamando < también.

Ahora me voy a desviar un poco más y hablaré sobre la pereza.

Probablemente también hayas escrito una función similar a esta:

contains :: (Ord a) => a -> Tree a -> Bool
contains _ EmptyTree = False
contains x (Node a left right) = case x `compare` a of
    EQ -> True
    ...

En el momento en que x == a, necesitas saber que el árbol usa el Node constructor, y que su primer argumento es igual a x. No necesita saber cuáles son los subárboles.

Pero ahora mire hacia atrás en mi definición de insert sobre. Cuando el árbol que se le da es un Node, siempre devuelve un Node cuyo primer argumento es siempre a. Pero no lo dice por adelantado: en su lugar, evalúa x `compare` a.

podemos reescribir insert para realizar la comparación lo más tarde posible:

insert :: (Ord a) => a -> Tree a -> Tree a
insert x EmptyTree = Node x EmptyTree EmptyTree
insert x (Node a left right) = Node a newLeft newRight
  where comparison = x `compare` a
        newLeft  = if comparison == LT then insert x left  else left
        newRight = if comparison == GT then insert x right else right

Ahora devolvemos el Node a bit tan pronto como sea posible --- ¡incluso si la comparación arroja un error! --- y todavía realizamos la comparación una vez como máximo.

contestado el 22 de mayo de 12 a las 11:05

muy interesante digresión, especialmente la parte sobre la pereza! Y muchas gracias por apoyar mi respuesta :) - ricardo t

GHC no puede inferir si sus tres guardias en el insert x (Node a left right) cubrir todos los casos posibles y, en consecuencia, no habrá ningún organismo con el que asociarse insert x (Node a left right). Intenta reemplazar la última condición. x > a con otherwise (un sinónimo de True). Sin embargo, en este caso específico, es cierto que los guardias no cubren todos los casos, vea el ejemplo de augusss sobre los números dobles.

contestado el 22 de mayo de 12 a las 18:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.