Declaraciones newtype
en Haskell
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*XSupongamos que ahora declaramos un nuevo tipo de datos que simplemente "arropa" un
Int
data Drawer = Drawer IntPreparemos 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*xGHC 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* xBien, 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 IntAhora la correspondiente función análoga a
alpha y beta
fakebeta::FakeDrawer->Int fakebeta (FakeDrawer x) = 2*xVeamos 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.