En una datadeclaración, un constructor de tipo es lo que está en el lado izquierdo del signo igual. Los constructores de datos son las cosas en el lado derecho del signo igual. Utiliza constructores de tipos donde se espera un tipo y usa constructores de datos donde se espera un valor.
Constructores de datos
Para simplificar las cosas, podemos comenzar con un ejemplo de un tipo que representa un color.
data Colour = Red | Green | Blue
Aquí tenemos tres constructores de datos. Coloures un tipo y Greenes un constructor que contiene un valor de tipo Colour. Del mismo modo, Redy Blueson constructores que construyen valores de tipo Colour. ¡Aunque podríamos imaginar condimentarlo!
data Colour = RGB Int Int Int
Todavía tenemos el tipo Colour, pero RGBno es un valor, es una función que toma tres Ints y devuelve un valor. RGBtiene el tipo
RGB :: Int -> Int -> Int -> Colour
RGBes un constructor de datos que es una función que toma algunos valores como argumentos, y luego los usa para construir un nuevo valor. Si ha realizado alguna programación orientada a objetos, debe reconocer esto. ¡En OOP, los constructores también toman algunos valores como argumentos y devuelven un nuevo valor!
En este caso, si aplicamos RGBa tres valores, ¡obtenemos un valor de color!
Prelude> RGB 12 92 27
#0c5c1b
Hemos construido un valor de tipo Colouraplicando el constructor de datos. Un constructor de datos contiene un valor como lo haría una variable o toma otros valores como argumento y crea un nuevo valor . Si ha realizado una programación previa, este concepto no debería serle muy extraño.
Descanso
Si desea construir un árbol binario para almacenar Strings, puede imaginar hacer algo como
data SBTree = Leaf String
| Branch String SBTree SBTree
Lo que vemos aquí es un tipo SBTreeque contiene dos constructores de datos. En otras palabras, hay dos funciones (a saber, Leafy Branch) que construirán valores del SBTreetipo. Si no está familiarizado con el funcionamiento de los árboles binarios, simplemente espere allí. En realidad, no necesita saber cómo funcionan los árboles binarios, solo que este almacena Strings de alguna manera.
También vemos que ambos constructores de datos toman un Stringargumento: esta es la cadena que van a almacenar en el árbol.
¡Pero! Y si también quisiéramos poder almacenar Bool, tendríamos que crear un nuevo árbol binario. Podría verse más o menos así:
data BBTree = Leaf Bool
| Branch Bool BBTree BBTree
Constructores tipo
Ambos SBTreey BBTreeson constructores tipo. Pero hay un problema evidente. ¿Ves lo similares que son? Esa es una señal de que realmente quieres un parámetro en alguna parte.
Entonces podemos hacer esto:
data BTree a = Leaf a
| Branch a (BTree a) (BTree a)
Ahora presentamos una variable de tipo a como parámetro para el constructor de tipos. En esta declaración, se BTreeha convertido en una función. Toma un tipo como argumento y devuelve un nuevo tipo .
Aquí es importante considerar la diferencia entre un tipo concreto (los ejemplos incluyen Int, [Char]y Maybe Bool) que es un tipo que puede asignarse a un valor en su programa, y una función de constructor de tipo que necesita alimentar un tipo para poder ser asignado a un valor. Un valor nunca puede ser del tipo "lista", porque debe ser una "lista de algo ". En el mismo espíritu, un valor nunca puede ser del tipo "árbol binario", porque debe ser un "árbol binario que almacene algo ".
Si pasamos, digamos, Boolcomo argumento a BTree, devuelve el tipo BTree Bool, que es un árbol binario que almacena Bools. Reemplace cada aparición de la variable de tipo acon el tipo Bool, y puede ver por sí mismo cómo es cierto.
Si lo desea, puede ver BTreecomo una función con el tipo
BTree :: * -> *
Los tipos son algo así como tipos: *indica un tipo concreto, por lo que decimos que BTreees de un tipo concreto a un tipo concreto.
Terminando
Regrese aquí un momento y tome nota de las similitudes.
Los constructores de datos con parámetros son geniales si queremos ligeras variaciones en nuestros valores: colocamos esas variaciones en los parámetros y dejamos que el tipo que crea el valor decida qué argumentos van a presentar. En el mismo sentido, los constructores de tipos con parámetros son geniales si queremos ligeras variaciones en nuestros tipos! Ponemos esas variaciones como parámetros y dejamos que el tipo que crea el tipo decida qué argumentos van a presentar.
Un caso de estudio
A medida que la casa se extiende aquí, podemos considerar el Maybe atipo. Su definición es
data Maybe a = Nothing
| Just a
Aquí, Maybehay un constructor de tipos que devuelve un tipo concreto. Justes un constructor de datos que devuelve un valor. Nothinges un constructor de datos que contiene un valor. Si miramos el tipo de Just, vemos que
Just :: a -> Maybe a
En otras palabras, Justtoma un valor de tipo ay devuelve un valor de tipo Maybe a. Si nos fijamos en el tipo de Maybe, vemos que
Maybe :: * -> *
En otras palabras, Maybetoma un tipo concreto y devuelve un tipo concreto.
¡Una vez más! La diferencia entre un tipo concreto y una función constructora de tipos. No puede crear una lista de Maybes, si intenta ejecutar
[] :: [Maybe]
Obtendrás un error. Sin embargo, puede crear una lista de Maybe Int, o Maybe a. Esto se debe a que Maybees una función de constructor de tipos, pero una lista debe contener valores de un tipo concreto. Maybe Inty Maybe ason tipos concretos (o si lo desea, llamadas a funciones de constructor de tipos que devuelven tipos concretos).
Cares un constructor de tipos (en el lado izquierdo del=) y un constructor de datos (en el lado derecho). En el primer ejemplo, elCarconstructor de tipo no toma argumentos, en el segundo ejemplo toma tres. En ambos ejemplos, elCarconstructor de datos toma tres argumentos (pero los tipos de esos argumentos son en un caso fijos y en el otro parametrizados).