2009-05-07 10 views
17

¿Se recomienda siempre tener coincidencias de patrones exhaustivas en Haskell, incluso para casos "imposibles"?¿Se recomienda siempre tener coincidencias de patrones exhaustivas en Haskell, incluso para casos "imposibles"?

Por ejemplo, en el siguiente código, estoy coincidiendo con el patrón en el "acumulador" de un foldr. Estoy en control total de los contenidos del acumulador, porque lo creo (no me lo pasa como entrada, sino que se construye dentro de mi función). Por lo tanto, sé que ciertos patrones nunca deberían coincidir. Si me esfuerzo por no obtener nunca el error "Patrón coincidente no es exhaustivo", entonces colocaría una coincidencia de patrón para él, simplemente error con el mensaje "Este patrón nunca debería ocurrir". Muy parecido a una afirmación en C#. No puedo pensar en otra cosa que hacer allí.

¿Qué práctica recomendaría en esta situación y por qué?

Aquí está el código:

gb_groupBy p input = foldr step [] input 
    where 
     step item acc = case acc of 
      []       -> [[item]] 
      ((x:xs):ys)     -> if p x item 
              then (item:x:xs):ys 
              else [item]:acc 

El patrón no coincide (según lo informado por el intérprete) es:

Advertencia: Coincidencia de patrón (es) no son exhaustivas En una alternativa caso: Patrones no coincide: []: _

+9

Lo molesto aquí es que para silenciar la advertencia a menudo termina poniendo un mensaje de error de tiempo de ejecución menos útil. Me gustaría simplemente reconocer el caso perdido pero permitir el uso del texto de error de tiempo de ejecución predeterminado (que apunta al número de archivo/línea). –

+0

Ese es un gran punto. Supongo que actualmente no hay forma de hacer eso. Demasiado. –

+0

ver también http://stackoverflow.com/questions/1882334 – sdcvvc

Respuesta

19

Esto es probablemente más una cuestión de estilo que otra cosa. En lo personal, me gustaría poner en un

_ -> error "Impossible! Empty list in step" 

aunque sólo sea para silenciar el aviso :)

+1

Estoy de acuerdo con este enfoque. Ayuda a indicar que intencionalmente no manejaste los otros casos porque piensas que es imposible, en lugar de simplemente olvidar o no darte cuenta. – newacct

+6

Siempre es divertido cuando su programa termina repentinamente con este mensaje descriptivo: *** Excepción: ¡Lo imposible sucedió! –

+0

Sí, me recuerda cuando era muy nuevo en la programación, y tenía comentarios como "Nunca deberías llegar a esta línea". :) –

10

Puede resolver la advertencia en este caso especial, al hacer esto:

gb_groupBy p input = foldr step [] input 
    where 
    step item acc = case acc of 
     []       -> [[item]] 
     (xs:xss)      -> if p (head xs) item 
             then (item:xs):xss 
             else [item]:acc 

La coincidencia de patrones es luego complete, y la condición "imposible" de una lista vacía en la cabecera del acumulador causaría un error de tiempo de ejecución pero no una advertencia.

Otra forma de ver el problema más general de los emparejamientos de patrones incompletos es considerarlos como un "olor a código", es decir, una indicación de que estamos tratando de resolver un problema de manera subóptima o no Haskellish y trata de reescribir nuestras funciones

Implementación de groupBy con un foldr hace que sea imposible aplicarlo a una lista infinita, que es un objetivo de diseño que las funciones de la lista Haskell intentan lograr siempre que sea semánticamente razonable. Considere

take 5 $ groupBy (==) someFunctionDerivingAnInfiniteList 

Si los primeros 5 grupos w.r.t. la igualdad es finita, la evaluación perezosa terminará. Esto es algo que no puedes hacer en un lenguaje estrictamente evaluado. Incluso si usted no trabaja con listas infinitas, escribiendo funciones como esto dará lugar a un mejor rendimiento en las listas largas, o evitar el desbordamiento de pila que se produce cuando la evaluación de expresiones como

take 5 $ gb_groupBy (==) [1..1000000] 

En List.hs, GroupBy se implementa como esto:

groupBy   :: (a -> a -> Bool) -> [a] -> [[a]] 
groupBy _ []  = [] 
groupBy eq (x:xs) = (x:ys) : groupBy eq zs 
          where (ys,zs) = span (eq x) xs 

Esto permite al intérprete/compilador para evaluar sólo las partes de la computación necesaria para el resultado. span produce un par de listas, donde la primera consiste en elementos (consecutivos) del encabezado de la lista que satisfacen un predicado, y el segundo es el resto de la lista. También se implementa para trabajar en listas infinitas.

+0

+1, perspicaz. –

+0

> Esto es algo que no puede hacer en un lenguaje estrictamente evaluado Esto se puede hacer en cualquier idioma de Turing completo ... Y a veces incluso no demasiado. Por ejemplo, puede modelar fácilmente listas "infinitas" incluso en Java. –

+1

El cálculo de Turing se refiere a qué tipo de cálculo puede hacer, no cómo lo especifica, siendo este último el alcance obvio de mi respuesta. Por supuesto, puede improvisar algo junto con, por ejemplo, las utilidades de la colección Guava en Java, o más elegantemente, crear algo usando LINQ en.RED. Pero esto requiere colecciones que no solo implementen Iterable o IEnumerable, sino que habiliten explícitamente el cálculo perezoso, p. implementando AbstractIterator de Guava o la palabra clave yield en C#. En Haskell, todo lo que devuelve una colección se puede usar de forma perezosa, sin más preámbulos. – Christoph

4

Para dar seguimiento a mi comentario anterior, me di cuenta de que hay una manera de reconocer el caso perdido pero aún obtener un error útil con el número de archivo/línea. No es ideal, ya que solo aparecerá en versiones sin optimizar (ver here).

... 
[]:xs -> assert False (error "unreachable because I know everything") 
+0

Agradable. Gracias por el seguimiento. –

7

Encuentro la comprobación exhaustiva de los patrones de casos indispensables. Intento nunca usar _ en un caso en el nivel superior, porque _ concuerda con todo, y al usarlo invalida el valor de la verificación exhaustiva. Esto es menos importante con las listas pero es muy importante con los tipos de datos algebraicos definidos por el usuario, porque quiero poder agregar un nuevo constructor y tener la barra de compilación en todos los casos faltantes. Por esta razón, siempre compilo con -Werror encendido, por lo que no hay manera de que pueda omitir un caso.

Como se observa, el código se puede ampliar con este caso

[] : _ -> error "this can't happen" 

Internamente, GHC tiene una función panic, que a diferencia de error dará las coordenadas de origen, pero yo miraba a la puesta en práctica y no podía hacer la cabeza o la cola de ella.

+2

Me gusta mucho como una guía. Cuando programo en C#, a menudo he deseado algo como "comprobación exhaustiva". Por ejemplo, si tuviera una enumeración y una declaración de caso para manejar cada miembro enumerado, quería poder decirle al compilador "asegúrese de haber cubierto todos los casos". No tenía idea de que ese concepto formaría parte de la base de Haskell y otros lenguajes funcionales. –

+0

Oh, sí, siempre me siento culpable cuando uso enums y switch. Pero es mucho más conciso que crear una jerarquía de toda la clase y dividir un método serio en muchos métodos virtuales pequeños. –

+0

Sí. Soy un gran creyente en el polimorfismo, pero incluso aparte de eso, hay casos en los que desea "inclinar" un valor dividiéndolo en subcategorías (menos de 3, 3 o más pero impar, y 3 o más, pero incluso). Si lo expresas incorrectamente, tienes errores "límite". Pero el compilador podría detectarlo fácilmente si supiera que está tratando de cubrir todos los rangos posibles. Y esa es una de las cosas que hace la coincidencia de patrones en muchos de los lenguajes funcionales. –

2

El sistema de tipos es su amigo, y la advertencia le indica que su función tiene grietas. El mejor enfoque es buscar un ajuste más limpio y elegante entre los tipos.

Considerar la definición de GHC de groupBy:

groupBy     :: (a -> a -> Bool) -> [a] -> [[a]] 
groupBy _ []   = [] 
groupBy eq (x:xs)  = (x:ys) : groupBy eq zs 
          where (ys,zs) = span (eq x) xs 
1

Mi punto de vista es que es un caso imposible indefinido.
Si no está definido, tenemos una función para él: el hábilmente llamado undefined.

Complete su coincidencia con los gustos de:

_ -> undefined 

Y ahí lo tienes!

Cuestiones relacionadas