2009-06-14 24 views
207

Encontré la siguiente definición cuando intento aprender a Haskell usando un proyecto real para conducirlo. No entiendo qué significa el signo de exclamación frente a cada argumento y mis libros no parecen mencionarlo.¿Qué significa el signo de exclamación en una declaración de Haskell?

data MidiMessage = MidiMessage !Int !MidiMessage 
+12

Sospecho que esta podría ser una pregunta muy común; Claramente recuerdo haberme preguntado exactamente lo mismo, hace mucho tiempo. –

Respuesta

255

Es una declaración de rigor. Básicamente, significa que debe evaluarse a lo que se denomina "forma débil de cabeza normal" cuando se crea el valor de la estructura de datos. Veamos un ejemplo, para que podamos ver lo que esto significa:

data Foo = Foo Int Int !Int !(Maybe Int) 

f = Foo (2+2) (3+3) (4+4) (Just (5+5)) 

La función f anterior, cuando se evalúa, devolverá un "thunk": es decir, el código para ejecutar averiguar su valor . En ese momento, un Foo ni siquiera existe, solo el código.

Pero en algún momento alguien puede tratar de buscar en su interior, probablemente a través de una comparación de patrones:

case f of 
    Foo 0 _ _ _ -> "first arg is zero" 
    _   -> "first arge is something else" 

Esto va a ejecutar código suficiente para hacer lo que necesita, y no más. Por lo tanto, creará un Foo con cuatro parámetros (porque no puede mirar dentro de él sin que exista). El primero, ya que lo estamos probando, tenemos que evaluar todo el camino hasta 4, donde nos damos cuenta de que no coincide.

No es necesario evaluar el segundo, porque no lo estamos probando. Por lo tanto, en lugar de 6 almacenados en esa ubicación de memoria, almacenaremos el código para una posible evaluación posterior, (3+3). Eso se convertirá en un 6 solo si alguien lo mira.

El tercer parámetro, sin embargo, tiene un ! en frente de ella, por lo que está estrictamente evaluado: se ejecuta (4+4), y 8 se almacena en esa ubicación de memoria.

El cuarto parámetro también se evalúa estrictamente. Pero aquí es donde se vuelve un poco complicado: estamos evaluando no completamente, pero solo a una forma de cabeza normal débil. Esto significa que averiguamos si es Nothing o Just algo, y lo almacenamos, pero no vamos más allá. Eso significa que no almacenamos Just 10, sino realmente Just (5+5), dejando el thunk dentro sin evaluar. Esto es importante de saber, aunque creo que todas las implicaciones de esto van más allá del alcance de esta pregunta.

Puede anotar argumentos de la función de la misma manera, si se habilita la extensión BangPatterns idioma:

f x !y = x*y 

f (1+1) (2+2) devolverá el golpe seco (1+1)*4.

+12

Esto es muy útil. Sin embargo, no puedo evitar preguntarme si la terminología de Haskell está complicando las cosas para que la gente las entienda con términos como "forma de cabeza débil y débil", "estricto", etc. Si te entiendo correctamente, ¡suena como el! operador simplemente significa almacenar el valor evaluado de una expresión en lugar de almacenar un bloque anónimo para evaluarlo más tarde. ¿Es esa una interpretación razonable o hay algo más para ella? – David

+61

@David La pregunta es hasta dónde se puede evaluar el valor. La forma normal de cabeza débil significa: evaluarlo hasta llegar al constructor más externo. La forma normal significa evaluar todo el valor hasta que no quedan componentes no evaluados. Debido a que Haskell permite todo tipo de niveles de profundidad de evaluación, tiene una rica terminología para describir esto. No tiende a encontrar estas distinciones en los idiomas que solo admiten la semántica llamada por valor. –

+7

@David: escribí una explicación más detallada aquí: [Haskell: ¿Qué es la forma normal de Weak Head?] (Http://stackoverflow.com/questions/6872898/haskell-what-is-weak-head-normal- form/6889335 # 6889335). Aunque no menciono los patrones de bang o las anotaciones de rigor, son equivalentes a usar 'seq'. – hammar

23

Creo que es una anotación de rigor.

Haskell es un lenguaje funcional puro , pero a veces la sobrecarga de pereza puede ser demasiado o un desperdicio. Entonces, para lidiar con eso, puede pedir al compilador que evalúe completamente los argumentos de una función en lugar de analizar los procesos.

Hay más información en esta página: Performance/Strictness.

+0

Hmm, parece que el ejemplo de la página a la que hace referencia habla del uso de! cuando invoca una función. Pero eso parece diferente de ponerlo en una declaración de tipo. ¿Qué me estoy perdiendo? – David

+0

Crear una instancia de un tipo también es una expresión. Puede pensar en los constructores de tipo como funciones que devuelven nuevas instancias del tipo especificado, dados los argumentos suministrados. –

+1

De hecho, para todos los efectos, los constructores de tipo * son * funciones; puede aplicarlos parcialmente, pasarlos a otras funciones (por ejemplo, 'map Just [1,2,3]' para obtener [Just 1, Just 2, Just 3]) y así sucesivamente. Encuentro útil pensar en la posibilidad de combinar patrones con ellos, así como con una facilidad completamente diferente. –

67

Una manera simple de ver la diferencia entre los argumentos de constructor estrictos y no estrictos es cómo se comportan cuando están indefinidos.Dada

data Foo = Foo Int !Int 

first (Foo x _) = x 
second (Foo _ y) = y 

Dado que el argumento no estricto no es evaluada por second, pasando undefined no causa un problema:

> second (Foo undefined 1) 
1 

Pero el argumento no puede ser estricta undefined, incluso si no usamos el valor:

> first (Foo 1 undefined) 
*** Exception: Prelude.undefined 
+0

Esto es mejor que la respuesta de Curt Sampson porque en realidad explica los efectos observables por el usuario que tiene el símbolo '!', En lugar de profundizar en los detalles de la implementación interna. –

Cuestiones relacionadas