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
, Green
o Red
. Pero eso entra en conflicto con la forma en que entiendo los dos primeros ejemplos: ¿es que el tipo Car
solo 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.
Car
es un constructor de tipos (en el lado izquierdo del=
) y un constructor de datos (en el lado derecho). En el primer ejemplo, elCar
constructor de tipo no toma argumentos, en el segundo ejemplo toma tres. En ambos ejemplos, elCar
constructor 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
data
declaració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.
Colour
es un tipo yGreen
es un constructor que contiene un valor de tipoColour
. Del mismo modo,Red
yBlue
son constructores que construyen valores de tipoColour
. ¡Aunque podríamos imaginar condimentarlo!Todavía tenemos el tipo
Colour
, peroRGB
no es un valor, es una función que toma tres Ints y devuelve un valor.RGB
tiene el tipoRGB
es 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
RGB
a tres valores, ¡obtenemos un valor de color!Hemos construido un valor de tipo
Colour
aplicando 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
String
s, puede imaginar hacer algo comoLo que vemos aquí es un tipo
SBTree
que contiene dos constructores de datos. En otras palabras, hay dos funciones (a saber,Leaf
yBranch
) que construirán valores delSBTree
tipo. 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 almacenaString
s de alguna manera.También vemos que ambos constructores de datos toman un
String
argumento: 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
SBTree
yBBTree
son 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
a
como parámetro para el constructor de tipos. En esta declaración, seBTree
ha convertido en una función. Toma un tipo como argumento y devuelve un nuevo tipo .Si pasamos, digamos,
Bool
como argumento aBTree
, devuelve el tipoBTree Bool
, que es un árbol binario que almacenaBool
s. Reemplace cada aparición de la variable de tipoa
con el tipoBool
, y puede ver por sí mismo cómo es cierto.Si lo desea, puede ver
BTree
como una función con el tipoLos tipos son algo así como tipos:
*
indica un tipo concreto, por lo que decimos queBTree
es 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 a
tipo. Su definición esAquí,
Maybe
hay un constructor de tipos que devuelve un tipo concreto.Just
es un constructor de datos que devuelve un valor.Nothing
es un constructor de datos que contiene un valor. Si miramos el tipo deJust
, vemos queEn otras palabras,
Just
toma un valor de tipoa
y devuelve un valor de tipoMaybe a
. Si nos fijamos en el tipo deMaybe
, vemos queEn otras palabras,
Maybe
toma 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
Maybe
s, si intenta ejecutarObtendrás un error. Sin embargo, puede crear una lista de
Maybe Int
, oMaybe a
. Esto se debe a queMaybe
es una función de constructor de tipos, pero una lista debe contener valores de un tipo concreto.Maybe Int
yMaybe a
son 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 -> Z
puede, 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 Z
y 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
,Green
yBlue
).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
Eg1
valor es, básicamente, ya sea un entero o una cadena. Entonces, el conjunto de todos losEg1
valores 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 refierenEg1
como un "tipo de suma". Por otra parte:Cada
Eg2
valor consiste en tanto un número entero y una cadena. Por lo tanto, el conjunto de todos losEg2
valores 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
union
s, con una disciplina de etiqueta. :)union
, la gente me mira como "¿quién diablos usa eso ?" ;-)union
usos 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"
Color
que no toma argumentos, y tiene tres "constructores de datos"Blue
,Green
yRed
. Ninguno de los constructores de datos toma ningún argumento. Esto significa que hay tres de tipoColor
:Blue
,Green
yRed
.Se utiliza un constructor de datos cuando necesita crear un valor de algún tipo. Me gusta:
crea un valor
myFavoriteColor
utilizando elGreen
constructor de datos, ymyFavoriteColor
será de tipoColor
ya 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
Color
constructor 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
Green
constructor 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
Int
en 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
Color
constructor 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.a
indata Color a = Red a
.a
es 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) -> a
toma una tupla de dos valores (de tiposa
yb
) 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.
Nothing
es algo aburrido, no contiene datos útiles. Por otro lado,Just
contiene un valor dea
cualquier tipo quea
pueda tener. Escribamos una función que use este tipo, por ejemplo, obtener el encabezado de unaInt
lista, si hay alguna (espero que esté de acuerdo en que esto es más útil que arrojar un error):Entonces, en este caso
a
es 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
String
no 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 deCar
necesitaría cambiar para usar enCompany
lugar deString
para su primer valor. Su segunda implementación está bien, la usa comoCar Company String Int
y 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.data
onewtype
(por ejemplodata Bright = Bright Color
), o puede usarlotype
para definir un sinónimo (por ejemplotype Bright = Color
).El segundo tiene la noción de "polimorfismo".
El
a b c
puede ser de cualquier tipo. Por ejemplo,a
puede ser un[String]
,b
puede ser[Int]
yc
puede ser[Char]
.Mientras que el tipo del primero es fijo: la compañía es una
String
, el modelo es unaString
y 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) yInt
por año. En el segundo caso, eres más genérico.a
,,b
yc
pueden 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 suColor
tipo.fuente