2010-09-04 14 views

Respuesta

24

Una de las razones es denotational semantics of Haskell.

Una de las propiedades claras de las funciones de Haskell (pura) es su monotonicidad: un argumento más definido produce un valor más definido. Esta propiedad es muy importante, p. razonar sobre las funciones recursivas (lea el artículo para entender por qué).

Denotación de excepción por definición es la parte inferior, _|_, el elemento menos en poset correspondiente al tipo dado. Por lo tanto, para satisfacer el requisito de monotonía, la siguiente desigualdad tiene que mantener durante toda la denotación f de la función Haskell:

f(_|_) <= f(X) 

Ahora, si pudiéramos capturar excepciones, podríamos romper esta desigualdad por "reconocimiento" de la parte inferior (para controlar el excepción) y devolver el valor más definido:

f x = case catch (seq x True) (\exception -> False) of 
     True -> -- there was no exception 
      undefined 
     False -> -- there was an exception, return defined value 
      42 

Aquí está la demostración de trabajo completo (requiere base 4 Control.Exception):

import Prelude hiding (catch) 
import System.IO.Unsafe (unsafePerformIO) 
import qualified Control.Exception as E 

catch :: a -> (E.SomeException -> a) -> a 
catch x h = unsafePerformIO $ E.catch (return $! x) (return . h) 

f x = case catch (seq x True) (\exception -> False) of 
     True -> -- there was no exception 
      undefined 
     False -> -- there was an exception, return defined value 
      42 

Otra razón, como señaló TomMD, es romper la transparencia referencial. Podrías reemplazar cosas iguales por iguales y obtener otra respuesta. (Igual en sentido denotacional, es decir, denotan el mismo valor, no en el sentido ==)

¿Cómo lo haríamos? Considere la siguiente expresión:

let x = x in x 

Ésta es una recursividad no termina, por lo que nunca nos devuelve ninguna información y por lo tanto se denota también por _|_. Si hemos sido capaces de capturar las excepciones, podemos escribir la función f como

f undefined = 0 
f (let x = x in x) = _|_ 

(El último siempre es cierto para las funciones estrictas, ya que Haskell no proporciona medios para detectar la computación no terminar - y no puede, en principio, debido a Halting problem.)

+1

Bien. Este parece ser uno de los antecedentes matemáticos difíciles detrás de Haskell. Gracias por tu breve descripción. – fuz

+1

Lo siento, pero esta respuesta es completamente desconcertante por lo que creo que es una pregunta muy simple ... pero esto probablemente se refleja más en mi falta de comprensión de Haskell en lugar de su respuesta. –

+1

dodgy_coder: Lamento que no haya sido útil para ti. Puede haber diferentes perspectivas desde las cuales uno puede responder esta pregunta. Podría decir que no puede (no) hacerlo porque los tipos de funciones correspondientes lo permiten (no). Entonces, por supuesto, podría preguntar por qué los tipos son como son. La respuesta es "hay buenas razones para (no) permitir tales cosas", e intenté esbozar estas razones más arriba. –

14

Porque las excepciones pueden rotura referential transparency.

Probablemente esté hablando de excepciones que en realidad son resultados directos de la entrada. Por ejemplo:

head [] = error "oh no!" -- this type of exception 
head (x:xs) = x 

Si estás lamentando no ser capaz de detectar errores como éste entonces afirmo a usted que las funciones no deben ser confiando en error o cualquier otra excepción, sino que deben utilizar un tipo de retorno adecuada (Maybe, Either, o quizás MonadError). Esto te obliga a lidiar con la condición excepcional de una manera más explícita.

A diferencia de lo anterior (y lo que causa el problema detrás de su pregunta), las excepciones pueden ser de señales como las condiciones de falta de memoria que son completamente independientes del valor que se calcula. Esto claramente no es un concepto puro y debe vivir en IO.

+2

¿Puede dar un ejemplo de cómo romper la transparencia referencial de esta manera? –

+0

Entonces, ¿quieres decirme que los elogios no son la forma de pensar funcionalmente, así que generalmente debo tratar de evitarlos? ¡Estupendo! – fuz

+0

Roman: Lo que mencioné fue que si pudieras capturar excepciones en código puro y basar tu resultado en esas excepciones, dicha acción rompería la transparencia referencial. –

1

Podría estar equivocado en mi explicación, pero así es como lo entiendo.

Dado que las funciones son puras en Haskell, el compilador tiene el derecho de evaluarlas en el orden que desee y sigue produciendo el mismo resultado. Por ejemplo, la función dada:

square :: Int -> Int 
square x = x * x 

expresión square (square 2) se puede evaluar de diferentes maneras, pero siempre se reduce al mismo resultado que es 16.

Si llamamos square de otro lugar:

test x = if x == 2 
     then square x 
     else 0 

square x se puede evaluar más adelante, "fuera" de la función test cuando el valor es realmente necesario. En ese momento, la pila de llamadas puede ser completamente diferente de lo que cabría esperar en Java.

Entonces, incluso si quisiéramos captar una posible excepción lanzada por square, ¿dónde debe colocar la pieza catch?

Cuestiones relacionadas