¿Por qué GHC se queja de patrones no exhaustivos?
Frecuentes
Visto 2,160 veces
29
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)
.
3 Respuestas
52
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
42
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
, LT
y 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
27
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 haskell ghc non-exhaustive-patterns or haz tu propia pregunta.
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 quecompare
no puedes digamos que los 3 sonFalse
; tiene que declarar unoTrue
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). - paquetEl 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