2012-02-22 37 views
8

He escrito una función Haskell que se divide en una lista xs(init xs, last xs) así:valor inferior en Haskell

split xs = split' [] xs 
    where 
     split' acc (x:[]) = (reverse acc, x) 
     split' acc (x:xs) = split' (x:acc) xs 

Desde una lista vacía no se puede dividir de esta manera, no hay ninguna coincidencia para la lista vacía. Sin embargo, no quería simplemente la función error .... Por lo tanto he definido lo siguiente:

split [] = ([], undefined) 

Gracias a la evaluación perezosa por lo tanto puedo definir un seguro init cual simplemente devuelve la lista vacía de la lista vacía:

init' = fst . split 

¿Hay alguna manera de cómo podría detectar lo indefinido si trataba de acceder a él, de manera que

last' xs 
    | isUndefined (snd xs) = ... 
    | otherwise   = ... 

sí sé sobre Maybe y Either, y que esas son una mejor opción para expresar lo que quiero. Sin embargo, me preguntaba si hay una manera de detectar un valor real de indefinido, es decir, en términos de errores de captura, como capturar excepciones.

+3

La simple respuesta es NO. No es posible detectar indefinido. (Una respuesta más complicado es que se puede capturar las excepciones en el Momad IO, pero eso no es lo que quiere hacer aquí.) – augustss

Respuesta

10

Debido inferior subsume no terminación, la función isUndefined habría que resolver el problema de la parada y por lo tanto no puede existir

Pero tenga en cuenta que incluso si existiera, aún no podría decir si el valor indefinido en el 2º elemento de su tupla se puso allí a través de su función split o si el último elemento de la lista ya no estaba definido.

+1

Si existiera una función que resolvió el problema de detención, podríamos mostrar False, y por lo tanto, podríamos mostrar todo, incluso "podemos decir si el valor indefinido en el segundo elemento de tu tupla se puso ahí a través de tu función de división o si el último elemento de la lista ya estaba indefinido ". –

8

La función error no hace nada hasta que se evalúa, por lo que puede hacer algo como:

split [] = ([], error "split: empty list") 

last' = snd . split 
+0

La mejor respuesta aquí, en mi opinión, ya que responde a la siguiente pregunta scravy habría tenido de todos modos: " ¿Qué pongo en la cláusula 'de otra manera'?". –

+0

Lo sé, como dije "gracias a la evaluación perezosa" - poner un error es como poner indefinido allí. Quería detectar esto, pero como he aprendido, eso no es posible ya que la semántica del fondo lo prohíbe. – scravy

+0

+1 por usar 'error' con un mensaje informativo. La opción de OP de "No quise' error ... '" y el uso de 'undefined' principalmente conduce a la frustración cuando se depura:" Sí, pulso 'indefinido'; pero * ¿qué uso * de' undefined'? ". Esto es especialmente malo en (GHC) Haskell, ya que la pereza y la optimización agresiva pueden hacer que un programa compilado se ejecute en un orden inesperado. Los rastreos de pila se han agregado recientemente a GHC, pero siguen siendo opt-in y una especie de ilusión más que un registro verdadero de cómo se ejecuta realmente el binario. ¡Es mucho más fácil simplemente decirnos qué fue lo que salió mal en primer lugar! – Warbo

11

undefined no es mejor que usar error. De hecho, undefined en preámbulo es defined como

undefined = error "Prelude.undefined" 


Ahora, una función que no puede resultar en un error se llama una "función Total", es decir, es válido para todos los valores de entrada.

La función split ha implementado actualmente tiene la firma

split :: [a] -> ([a], a) 

Este es un problema, ya que el tipo de firma promete que el resultado siempre contiene una lista y un elemento, que es claramente imposible prever listas vacías de tipo genérico.

La manera canónica en Haskell de abordar esto es cambiar el tipo de firma para indicar que a veces no tenemos un valor válido para el segundo elemento.

split :: [a] -> ([a], Maybe a) 

Ahora usted puede escribir una aplicación adecuada para el caso en el que se obtiene una lista vacía

split [] = ([], Nothing) 
split xs = split' [] xs 
    where 
     split' acc (x:[]) = (reverse acc, Just x) 
     split' acc (x:xs) = split' (x:acc) xs 

Ahora se puede detectar el caso de valor perdido por el patrón de coincidencia

let (init', last') = split xs 
in case last' of 
    Nothing -> ... -- do something if we don't have a value 
    Just x -> ... -- do something with value x 
+3

Un poco irónico que se define indefinido;) –

+1

@DanBurton Bueno, si ese es su tipo de humor, tenga en cuenta que también es posible definir 'undefined' como éste:' indefinido = undefined'. Se comporta de manera diferente (esta definición no termina cuando se fuerza), pero semánticamente tiene el mismo valor (abajo). –

4

De el Haskell 2010 Language Report > Introduction # Values and Types

Los errores en Haskell son semánticamente equivalentes a ⊥ ("abajo"). Técnicamente, son indistinguibles de la no determinación, por lo que el lenguaje no incluye ningún mecanismo para detectar o actuar sobre los errores.

Para ser claros, undefined pretende ser una forma de insertar ⊥ en su programa, y ​​dado que (como se señaló Shang) undefined se define en términos de error, no hay, por lo tanto, "ningún mecanismo para detectar o actuando al undefined ".

+1

Si bien esto es correcto desde el punto de vista de Haskell 2010, vale la pena señalar que en GHC Haskell, algunos valores,, incluido 'error' se implementan como excepciones, que pueden quedar atrapados en la mónada' IO'. Sin embargo, hacerlo es generalmente indicativo de un mal diseño, ya que hay mejores formas de manejar errores en código puro. (Incluso hay una excepción 'NonTermination' que puede lanzarse cuando el tiempo de ejecución puede detectar la no terminación, aunque esto por supuesto no es confiable debido al problema de detención). – hammar

1

Aunque semánticamente hablando, la respuesta de Ingo es correcta, si está usando GHC, hay una forma de usar un par de funciones "inseguras" que, aunque no son perfectas, le pasan un cálculo de tipo IO a que contiene una excepción que devolverá True, funciona. Aunque es un poco engañoso :).

import Control.Exception 
import System.IO.Unsafe 
import Unsafe.Coerce 

isUndefined :: a -> Bool 
isUndefined x = unsafePerformIO $ catch ((unsafeCoerce x :: IO()) >> return False) (const $ return True :: SomeException -> IO Bool) 

Sé que esto es horrible, pero no obstante funciona. No va a detectar la no terminación sin embargo;)

+0

Esto se puede mejorar reemplazando "return False" por algo como "(\ e -> return $ show e ==" Prelude.undefined ")" con una precisión de casi el 100% en la práctica (a menos que alguien cree una excepción). de show return "Prelude.undefined", pero eso sería una tontería ...) –