2012-01-20 18 views
16

dado:¿Por qué compila este código Haskell?

uncurry :: (a-> b -> c) -> (a,b) -> c  
id :: a -> a 

Invocando uncurry id resultados en función del tipo: (b -> c, b) -> c

¿Cómo conseguimos este resultado?

¿Cómo se puede usar id (a -> a) como el primer parámetro para desencajar, que requiere una función (a -> b -> c)?

Respuesta

25

Es más fácil de entender si lo intentamos y lo mira desde el punto de haciendo los tipos funcionan: averiguar lo que tenemos que hacer para id 's tipo de conseguir que se adapte a la forma requerida por uncurry. Ya que tenemos:

id :: a -> a 

también tenemos:

id :: (b -> c) -> (b -> c) 

Esto se puede ver mediante la sustitución de b -> c para a en el tipo original de id, tal como se puede sustituir Int lugar cuando averiguar el tipo de id 42. A continuación, se puede quitar el paréntesis en el lado derecho, ya que (->) es asociativo por la derecha:

id :: (b -> c) -> b -> c 

mostrando ese tipo id 's se ajusta a la forma a -> b -> c, donde a es b -> c. En otras palabras, podemos remodelar el tipo de id para que se ajuste al formulario requerido simplemente especializando el tipo general que ya tiene.

Otra forma de entender esto es ver que uncurry ($) también tiene el tipo (b -> c, b) -> c. La comparación de las definiciones de id y ($):

id :: a -> a 
id a = a 

($) :: (a -> b) -> a -> b 
($) f x = f x 

podemos hacer la última definición más puntos gratis:

($) f = f 

momento en el que el hecho de que ($) es simplemente una especialización de id a una más específica tipo se vuelve claro.

+2

Gracias! ¡Eso está comenzando a tener sentido! ¡Todavía lo encuentro un poco como un spinout! – ssanj

+1

@ssanj: Si te sirve de consuelo, escribir esto como 'currir id' en lugar de'currir ($) 'es malo :) – ehird

+1

Y si quieres ser particularmente malvado, podrías escribir' (\ 'id \' 3) 'en lugar de' ($ 3) 'o defina' id' como 'infixr 0 \' id \ '' y úselo en lugar de '($)', como '(^ 2) \' id \ '1 + 2 * 3'. – Vitus

8

¿Cómo se puede usar id (a -> a) como primer parámetro para desencajar, que requiere una función (a -> b -> c)?

En realidad, uncurry requiere (a -> (b -> c)) function. ¿Puedes ver la diferencia? :)

Omitir paréntesis es malo (bueno, a veces). Hace que sea imposible para un novato descifrar Haskell. Por supuesto, después de haber reunido algo de experiencia con el idioma, sientes que ya no los necesitas.

En este caso, todo se aclara una vez que escribimos todos los paréntesis omitidas volver explícitamente:

uncurry :: (a -> (b -> c)) -> ((a,b) -> c) 
id  :: a -> a 

Ahora, escribiendo uncurry id llamadas para una unificación tipo de a1 -> a1 con a2 -> (b -> c). Esto es sencillo, a1 ~ a2 y a1 ~ (b -> c). Sólo cosas mecánicas, no hay pensamiento creativo involucrado aquí. Así que id en cuestión tiene realmente el tipo a -> a where a ~ (b -> c), y así uncurry id tiene el tipo (b -> c,b) -> c, por simple sustitución de a ~ (b -> c) en (a,b) -> c. Es decir, espera un par de una función b -> c y un valor b, y debe producir un valor de c.

Dado que los tipos son más general (es decir, no se sabe nada acerca de ellos, y lo que no hay funciones específicas para llamar que podrían hacer el truco de alguna forma especial), el única manera para producir un valor c aquí es llame a la función b -> c con el valor b como argumento. Naturalmente, eso es lo que hace ($). Así que uncurry id == uncurry ($), aunque id es sin duda no($).

+0

'uncurry f (x, y) = f x y; id z = z; uncurry id (x, y) = id x y = x y; uncurry id = \ (x, y) -> x y; uncurry id :: (a -> b, a) -> b'. –

Cuestiones relacionadas