Declaraciones newtype
en Haskell

2011-12-27 por @joseanpg


Antes de explicar la utilidad de newtype creo conveniente que veamos que hace GHC cuando se encuentra con declaraciones en la que se ha utilizado.

¿Qué hace GHC con newtype?

Consideremos la siguiente función

alpha::Int->Int
alpha x = 2*X
Supongamos que ahora declaramos un nuevo tipo de datos que simplemente "arropa" un Int
data Drawer = Drawer Int
Preparemos ahora una función que actúe sobre un Drawer de la misma manera que alpha actua sobre su argumento
beta::Drawer->Int
beta (Drawer x) = 2*x
GHC transforma el anterior código Haskell en el código Core equivalente a
alpha = \ x -> 2*x
beta  = \ ds -> case ds of Drawer x -> 2* x
Bien, ahora demos paso a nuestro protagonista. Creemos un nuevo tipo de datos que también "arrope" a un Int pero en lugar de usar data usaremos newtype. El nombre del nuevo tipo que voy a crear es una pista importante :)
newtype FakeDrawer = FakeDrawer Int
Ahora la correspondiente función análoga a alpha y beta
fakebeta::FakeDrawer->Int
fakebeta (FakeDrawer x) = 2*x
Veamos en que se convierte tras pasar las primeras etapas de GHC
fakebeta = \ ds -> 2 * (cast ds (FakeDrawer ~ Int))

Podemos observar que el código de FakeDrawer se parece más al de la función inicial, alpha, que al de la función que corresponde a Drawer, beta. Realicemos una α-conversion en el cuerpo de FakeDrawer y reunamos las funciones para que se puedan comparar mejor

beta     = \ ds -> case ds of Drawer x -> 2 * x
alpha    = \ x -> 2 * x
fakebeta = \ x -> 2 * (cast x (FakeDrawer ~ Int))

cast es una función interna de GHC cuyo único objetivo es tratar modificar el tipo de una expresión, y en este caso convierte el tipo del argumento (que era FakeDrawer) en Int. El efecto que tiene es análogo a cambiar una "etiqueta" ya que en realidad ... ¡el argumento era desde el principio un simple Int "etiquetado" como FakeDrawer!

Es decir, al contrario que Drawer, FakeDrawer no aporta ninguna estructura contenedora nueva, tan sólo coloca un nuevo nombre a un "viejo tipo".

Resumiendo, de momento podemos decir que newtype es gramaticalmente equivalente a data pero semánticamente no aporta nada más que una nueva etiqueta para tipos previamente existentes.

Por supuesto la historia no termina aquí.

newtype vs. type

De los explicado en la sección anterior podría deducirse que newtype es prácticamente equivalente a type. Sería una interpretación erronea.

Con type creamos un alias de un tipo pero de manera que cada valor creado con los correspondientes constructores queda "etiquetado" con el nombre del tipo original: no se está creando ningún tipo nuevo. Con type podemos pensar que simplemente estamos creando un simple macro de sustitución directa.

En cambio con newtype sí estamos introduciendo en la lista de tipos uno nuevo, vacío de significado, pero un tipo más. Los valores "creados" con su constructor estarán etiquetados con el tipo en cuestión, en realidad es la única función que realiza el constructor, ya que la estructura la crea el constructor del tipo subyacente que estamos renombrando. Por supuesto cuando va a utilizarse uno de estos valores su tipo es sometido a una conversión forzada, como hemos visto en la sección anterior.

Cómo se usa newtype

[En construcción ...]