Estoy aprendiendo Haskell de learnyouahaskell.com . Tengo problemas para comprender los constructores de tipos y los constructores de datos. Por ejemplo, realmente no entiendo la diferencia entre esto:
data Car = Car { company :: String
, model :: String
, year :: Int
} deriving (Show)
y esto:
data Car a b c = Car { company :: a
, model :: b
, year :: c
} deriving (Show)
Entiendo que el primero es simplemente usar un constructor ( Car) para construir datos de tipo Car. Realmente no entiendo el segundo.
Además, ¿cómo se definen los tipos de datos de esta manera:
data Color = Blue | Green | Red
encajar en todo esto?
Por lo que entiendo, el tercer ejemplo ( Color) es un tipo que puede estar en tres estados: Blue, Greeno Red. Pero eso entra en conflicto con la forma en que entiendo los dos primeros ejemplos: ¿es que el tipo Carsolo puede estar en un estado Car, que puede tomar varios parámetros para construir? Si es así, ¿cómo encaja el segundo ejemplo?
Esencialmente, estoy buscando una explicación que unifique los tres ejemplos / construcciones de código anteriores.

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).Car :: String -> String -> Int -> Car) para construir datos de tipoCar. el segundo es simplemente usar un constructor de datos (Car :: a -> b -> c -> Car a b c) para construir datos de tipoCar a b c.Respuestas:
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.
Aquí tenemos tres constructores de datos.
Coloures un tipo yGreenes un constructor que contiene un valor de tipoColour. Del mismo modo,RedyBlueson constructores que construyen valores de tipoColour. ¡Aunque podríamos imaginar condimentarlo!Todavía tenemos el tipo
Colour, peroRGBno es un valor, es una función que toma tres Ints y devuelve un valor.RGBtiene el tipoRGBes 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!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 comoLo que vemos aquí es un tipo
SBTreeque contiene dos constructores de datos. En otras palabras, hay dos funciones (a saber,LeafyBranch) que construirán valores delSBTreetipo. 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 almacenaStrings 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í:Constructores tipo
Ambos
SBTreeyBBTreeson 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:
Ahora presentamos una variable de tipo
acomo parámetro para el constructor de tipos. En esta declaración, seBTreeha convertido en una función. Toma un tipo como argumento y devuelve un nuevo tipo .Si pasamos, digamos,
Boolcomo argumento aBTree, devuelve el tipoBTree Bool, que es un árbol binario que almacenaBools. Reemplace cada aparición de la variable de tipoacon el tipoBool, y puede ver por sí mismo cómo es cierto.Si lo desea, puede ver
BTreecomo una función con el tipoLos tipos son algo así como tipos:
*indica un tipo concreto, por lo que decimos queBTreees de un tipo concreto a un tipo concreto.Terminando
Regrese aquí un momento y tome nota de las similitudes.
Un constructor de datos es una "función" que toma 0 o más valores y le devuelve un nuevo valor.
Un constructor de tipos es una "función" que toma 0 o más tipos y le devuelve un nuevo tipo.
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 esAquí,
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 deJust, vemos queEn otras palabras,
Justtoma un valor de tipoay devuelve un valor de tipoMaybe a. Si nos fijamos en el tipo deMaybe, vemos queEn 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 ejecutarObtendrás un error. Sin embargo, puede crear una lista de
Maybe Int, oMaybe a. Esto se debe a queMaybees una función de constructor de tipos, pero una lista debe contener valores de un tipo concreto.Maybe IntyMaybe ason tipos concretos (o si lo desea, llamadas a funciones de constructor de tipos que devuelven tipos concretos).fuente
data Colour = Red | Green | Blue"no tenemos ningún constructor en absoluto" es totalmente errónea. Los constructores de tipos y los constructores de datos no necesitan tomar argumentos, véase, por ejemplo, haskell.org/haskellwiki/Constructor, que señala que endata Tree a = Tip | Node a (Tree a) (Tree a)"hay dos constructores de datos, Tip y Node".-XEmptyDataDecls) que te permite hacer eso. Como, como usted dice, no hay valores con ese tipo, una funciónf :: Int -> Zpuede, por ejemplo, no volver nunca (porque ¿qué devolvería?) Sin embargo, pueden ser útiles para cuando desea tipos pero realmente no le importan los valores .:k Zy me dio una estrella.Haskell tiene tipos de datos algebraicos , que muy pocos otros idiomas tienen. Esto es quizás lo que te confunde.
En otros idiomas, generalmente puede hacer un "registro", "estructura" o similar, que tiene un montón de campos con nombre que contienen varios tipos diferentes de datos. También puede a veces hacer una "enumeración", que tiene una (pequeña) conjunto de valores fijos posibles (por ejemplo, tu
Red,GreenyBlue).En Haskell, puede combinar ambos al mismo tiempo. Extraño, pero cierto!
¿Por qué se llama "algebraico"? Bueno, los nerds hablan de "tipos de suma" y "tipos de producto". Por ejemplo:
Un
Eg1valor es, básicamente, ya sea un entero o una cadena. Entonces, el conjunto de todos losEg1valores posibles es la "suma" del conjunto de todos los valores enteros posibles y todos los valores de cadena posibles. Por lo tanto, los nerds se refierenEg1como un "tipo de suma". Por otra parte:Cada
Eg2valor consiste en tanto un número entero y una cadena. Por lo tanto, el conjunto de todos losEg2valores posibles es el producto cartesiano del conjunto de todos los enteros y el conjunto de todas las cadenas. Los dos conjuntos se "multiplican" juntos, por lo que este es un "tipo de producto".Los tipos algebraicos de Haskell son tipos de suma de tipos de productos . Le da a un constructor múltiples campos para hacer un tipo de producto, y tiene múltiples constructores para hacer una suma (de productos).
Como ejemplo de por qué eso podría ser útil, suponga que tiene algo que genera datos como XML o JSON, y requiere un registro de configuración, pero obviamente, las configuraciones de configuración para XML y JSON son totalmente diferentes. Entonces podrías hacer algo como esto:
(Con algunos campos adecuados allí, obviamente). No puede hacer cosas como esta en lenguajes de programación normales, por lo que la mayoría de la gente no está acostumbrada.
fuente
unions, con una disciplina de etiqueta. :)union, la gente me mira como "¿quién diablos usa eso ?" ;-)unionusos en mi carrera en C. No hagas que parezca innecesario porque ese no es el caso.Comience con el caso más simple:
Esto define un "constructor de tipos"
Colorque no toma argumentos, y tiene tres "constructores de datos"Blue,GreenyRed. Ninguno de los constructores de datos toma ningún argumento. Esto significa que hay tres de tipoColor:Blue,GreenyRed.Se utiliza un constructor de datos cuando necesita crear un valor de algún tipo. Me gusta:
crea un valor
myFavoriteColorutilizando elGreenconstructor de datos, ymyFavoriteColorserá de tipoColorya que ese es el tipo de valores producidos por el constructor de datos.Se utiliza un constructor de tipos cuando necesita crear un tipo de algún tipo. Este suele ser el caso al escribir firmas:
En este caso, está llamando al
Colorconstructor de tipos (que no toma argumentos).¿Aún conmigo?
Ahora, imagine que no solo desea crear valores rojos / verdes / azules sino que también desea especificar una "intensidad". Como, un valor entre 0 y 256. Puede hacerlo agregando un argumento a cada uno de los constructores de datos, de modo que termine con:
Ahora, cada uno de los tres constructores de datos toma un argumento de tipo
Int. El constructor de tipos (Color) todavía no toma ningún argumento. Entonces, mi color favorito es un verde oscuro, podría escribirY nuevamente, llama al
Greenconstructor de datos y obtengo un valor de tipoColor.Imagínese si no quiere dictar cómo las personas expresan la intensidad de un color. Algunos pueden querer un valor numérico como acabamos de hacer. Otros pueden estar bien con solo un booleano que indica "brillante" o "no tan brillante". La solución a esto es no codificar
Inten los constructores de datos, sino usar una variable de tipo:Ahora, nuestro constructor de tipos toma un argumento (¡otro tipo que acabamos de llamar
a!) Y todos los constructores de datos tomarán un argumento (¡un valor!) De ese tipoa. Entonces podrías tenero
Observe cómo llamamos al
Colorconstructor de tipos con un argumento (otro tipo) para obtener el tipo "efectivo" que será devuelto por los constructores de datos. Esto toca el concepto de tipos sobre los que es posible que desee leer con una taza de café o dos.Ahora descubrimos qué son los constructores de datos y los constructores de tipos, y cómo los constructores de datos pueden tomar otros valores como argumentos y los constructores de tipos pueden tomar otros tipos como argumentos. HTH
fuente
->la firma.aindata Color a = Red a.aes un marcador de posición para un tipo arbitrario. Sin embargo, puede tener lo mismo en funciones simples, por ejemplo, una función de tipo(a, b) -> atoma una tupla de dos valores (de tiposayb) y produce el primer valor. Es una función "genérica", ya que no dicta el tipo de los elementos de la tupla, solo especifica que la función produce un valor del mismo tipo que el primer elemento de la tupla.Now, our type constructor takes one argument (another type which we just call a!) and all of the data constructors will take one argument (a value!) of that type a.Esto es muy útil.Como otros señalaron, el polimorfismo no es tan útil aquí. Veamos otro ejemplo con el que probablemente ya estés familiarizado:
Este tipo tiene dos constructores de datos.
Nothinges algo aburrido, no contiene datos útiles. Por otro lado,Justcontiene un valor deacualquier tipo queapueda tener. Escribamos una función que use este tipo, por ejemplo, obtener el encabezado de unaIntlista, si hay alguna (espero que esté de acuerdo en que esto es más útil que arrojar un error):Entonces, en este caso
aes unInt, pero funcionaría tan bien para cualquier otro tipo. De hecho, puede hacer que nuestra función funcione para cada tipo de lista (incluso sin cambiar la implementación):Por otro lado, puede escribir funciones que aceptan solo un cierto tipo de
Maybe, por ejemplo,En resumen, con el polimorfismo, le da a su propio tipo la flexibilidad para trabajar con valores de otros tipos diferentes.
En su ejemplo, puede decidir en algún momento que
Stringno es suficiente para identificar a la empresa, pero que debe tener su propio tipoCompany(que contiene datos adicionales como el país, la dirección, las cuentas pendientes, etc.). Su primera implementación deCarnecesitaría cambiar para usar enCompanylugar deStringpara su primer valor. Su segunda implementación está bien, la usa comoCar Company String Inty funcionaría como antes (por supuesto, las funciones de acceso a los datos de la empresa deben cambiarse).fuente
data Color = Blue ; data Bright = Color? Lo probé en ghci, y parece que el Color en el constructor de tipos no tiene nada que ver con el constructor de datos de Color en la definición de Bright. Solo hay 2 constructores de color, uno que es Data y el otro es Type.dataonewtype(por ejemplodata Bright = Bright Color), o puede usarlotypepara definir un sinónimo (por ejemplotype Bright = Color).El segundo tiene la noción de "polimorfismo".
El
a b cpuede ser de cualquier tipo. Por ejemplo,apuede ser un[String],bpuede ser[Int]ycpuede ser[Char].Mientras que el tipo del primero es fijo: la compañía es una
String, el modelo es unaStringy el año esInt.El ejemplo de Car podría no mostrar la importancia del uso del polimorfismo. Pero imagine que sus datos son del tipo de lista. Una lista puede contener
String, Char, Int ...En esas situaciones, necesitará la segunda forma de definir sus datos.En cuanto a la tercera forma, no creo que deba encajar en el tipo anterior. Es solo otra forma de definir datos en Haskell.
Esta es mi humilde opinión como principiante.
Por cierto: asegúrese de entrenar bien su cerebro y sentirse cómodo con esto. Es la clave para entender a Monad más tarde.
fuente
Se trata de tipos : en el primer caso, establezca los tipos
String(por empresa y modelo) yIntpor año. En el segundo caso, eres más genérico.a,,bycpueden ser los mismos tipos que en el primer ejemplo, o algo completamente diferente. Por ejemplo, puede ser útil dar el año como cadena en lugar de entero. Y si lo desea, incluso puede usar suColortipo.fuente