28

Este es un seguimiento de Why am I getting "Non-exhaustive patterns in function..." when I invoke my Haskell substring function?En Haskell, ¿por qué los patrones no exhaustivos no son errores en tiempo de compilación?

entiendo que el uso de -Wall, GHC puede advertir en contra de los patrones no exhaustivos. Me pregunto ¿cuál es la razón detrás no por lo que es un error en tiempo de compilación por defecto dado que siempre es posible definir explícitamente una función parcial:

f :: [a] -> [b] -> Int 
f [] _ = error "undefined for empty array" 
f _ [] = error "undefined for empty array" 
f (_:xs) (_:ys) = length xs + length ys 

La cuestión no es GHC-específica.

¿Es porque ...

  • nadie quería hacer cumplir un compilador de Haskell para llevar a cabo este tipo de análisis?
  • una búsqueda de patrón no exhaustivo puede encontrar algunos, pero no todos los casos?
  • funciones parcialmente definidas se consideran legítimas y se utilizan con la suficiente frecuencia como para no imponer el tipo de constructo que se muestra arriba? Si este es el caso, ¿puede explicarme por qué los patrones no exhaustivos son útiles/legítimos?

Respuesta

33

Hay casos en los que no te importa que una coincidencia de patrón no sea exhaustiva. Por ejemplo, si bien esto podría no ser la óptima implementación, no creo que ayudaría si no se compila:

fac 0 = 1 
fac n | n > 0 = n * fac (n-1) 

Que esto no es exhaustiva (los números negativos no encontró ningún caso) realmente no importa para el uso típico de la función factorial.

Además, no puede generalmente ser posible decidir por el compilador si una comparación de patrones es exhaustiva:

mod2 :: Integer -> Integer 
mod2 n | even n = 0 
mod2 n | odd n = 1 

Aquí todos los casos deben ser cubiertos, pero el compilador probablemente no puede detectarlo. Como los guardias pueden ser arbitrariamente complejos, el compilador no siempre puede decidir si los patrones son exhaustivos. Por supuesto, este ejemplo sería mejor escrito con otherwise, pero creo que también debería compilarse en su forma actual.

+4

Otro caso algo común sería declaraciones lambda dentro de una rama de guard/case/if. Usted sabe que el argumento tiene cierta forma debido a la rama en la que se encuentra, por lo que incluirla en el lambda es innecesario. –

+1

No creo que deba escribirse de otra manera. Es una gran definición de mod2 incluso si el compilador cree que no es exigente. Llegué a esta pregunta porque recibí este error escribiendo una función fib que no debería definirse para enteros negativos. –

+0

quizás esto esté fuera de alcance, pero ¿está decidiendo si una coincidencia de patrón es exhaustiva o no está relacionada con el problema de detención? intuitivamente, parece que un lenguaje tan estrictamente tipeado como haskell debería permitir este tipo de análisis, al menos para subconjuntos del lenguaje, pero sé que los instintos intestinales suelen ser una forma horrible de adivinar si algo es o no computable. – kai

12

Puede usar -Werror para convertir advertencias en errores. No sé si puedes convertir solo las advertencias de patrones no exhaustivos en errores, ¡lo siento!

En cuanto a la tercera parte de su pregunta:

A veces escribo una serie de funciones que tienden a trabajar estrechamente juntos y tienen propiedades que no se puede expresar fácilmente en Haskell. Al menos algunas de estas funciones tienden a tener patrones no exhaustivos, generalmente los "consumidores". Esto aparece, por ejemplo, en funciones que son inversas 'de tipo' entre sí.

Un ejemplo de juguete:

duplicate :: [a] -> [a] 
duplicate [] = [] 
duplicate (x:xs) = x : x : xs 

removeDuplicates :: Eq a => [a] -> [a] 
removeDuplicates [] = [] 
removeDuplicates (x:y:xs) | x == y = x : removeDuplicates xs 

Ahora es bastante fácil ver que removeDuplicates (duplicate as) es igual a as (siempre que el tipo de elemento está en Eq), pero en general duplicate (removeDuplicates bs) se colgará, porque hay un número impar de elementos o 2 elementos consecutivos difieren. Si no falla, es porque bs fue producido por (o podría haber sido producido por) duplicate en primer lugar.

Así que tienen las siguientes leyes (no válido Haskell):

removeDuplicates . duplicate == id 
duplicate . removeDuplicates == id (for values in the range of duplicate) 

Ahora, si desea evitar que los patrones no exhaustivos aquí, usted podría hacer removeDuplicates retorno Maybe [a], o añadir mensajes de error a los desaparecidos casos. Incluso se podría hacer algo en la línea de

newtype DuplicatedList a = DuplicatedList [a] 

duplicate :: [a] -> DuplicatedList a 
removeDuplicates :: Eq a => DuplicatedList a -> [a] 
-- implementations omitted 

Todo esto es necesario, porque no se puede expresar fácilmente 'ser una lista de longitud uniforme, con pares consecutivos de elementos en igualdad de condiciones' en el sistema de tipos de Haskell (a menos que seas Oleg :)

Pero si no exportas removeDuplicates, creo que está bien usar patrones no exhaustivos aquí. ¡Tan pronto como lo exportes, perderás el control sobre las entradas y tendrás que ocuparte de los casos que faltan!

+0

Supongo que se refería a removeDuplicates (x: y: xs) | x == y = x: removeDuplicates xs. – gawi

+0

¡Gracias, lo arreglé! – yatima2975

Cuestiones relacionadas