tutorial online logo empresa descargar constructora company haskell

online - haskell pdf



Haskell Type vs Data Constructor (6)

Comience con el caso más simple:

data Color = Blue | Green | Red

Esto define un Color "constructor de tipo" que no toma argumentos, y tiene tres "constructores de datos", Blue , Green y Red . Ninguno de los constructores de datos toma ningún argumento. Esto significa que hay tres de tipo Color : Blue , Green y Red .

Un constructor de datos se usa cuando necesita crear un valor de algún tipo. Me gusta:

myFavoriteColor :: Color myFavoriteColor = Green

crea un valor myFavoriteColor usando el constructor de datos Green - y myFavoriteColor será de tipo Color ya que ese es el tipo de valores producidos por el constructor de datos.

Un constructor de tipo se usa cuando necesita crear un tipo de algún tipo. Este suele ser el caso al escribir firmas:

isFavoriteColor :: Color -> Bool

En este caso, está llamando al constructor de tipo Color (que no toma argumentos).

¿Aún conmigo?

Ahora, imagine que no solo quería crear valores rojos / verdes / azules sino que también quería especificar una "intensidad". Como, un valor entre 0 y 256. Podría hacer eso agregando un argumento a cada uno de los constructores de datos, de modo que termine con:

data Color = Blue Int | Green Int | Red Int

Ahora, cada uno de los tres constructores de datos toma un argumento de tipo Int . El constructor de tipos ( Color ) aún no toma ningún argumento. Entonces, mi color favorito es un verde oscuro, podría escribir

myFavoriteColor :: Color myFavoriteColor = Green 50

Y de nuevo, llama al constructor de datos Green y obtengo un valor de tipo Color .

Imagina si no quieres dictar cómo las personas expresan la intensidad de un color. Algunos podrían querer un valor numérico como acabamos de hacer. Otros pueden estar bien con solo un booleano que indique "brillante" o "no tan brillante". La solución a esto es no codificar el Int en los constructores de datos sino usar una variable de tipo:

data Color a = Blue a | Green a | Red a

Ahora, nuestro constructor de tipos toma un argumento (¡otro tipo que llamamos a !) Y todos los constructores de datos tomarán un argumento (¡un valor!) De ese tipo a . Entonces podrías tener

myFavoriteColor :: Color Bool myFavoriteColor = Green False

o

myFavoriteColor :: Color Int myFavoriteColor = Green 50

Observe cómo llamamos al constructor de tipo Color con un argumento (otro tipo) para obtener el tipo "efectivo" que será devuelto por los constructores de datos. Esto toca el concepto de kinds que querrá leer durante una taza de café o dos.

Ahora descubrimos qué constructores de datos y constructores de tipos son, 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.

Estoy aprendiendo Haskell de learnyouahaskell.com . Tengo problemas para entender 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 compilar? Si es así, ¿cómo encaja el segundo ejemplo?

Básicamente, estoy buscando una explicación que unifique los tres ejemplos / constructos de código anteriores.


Como otros señalaron, el polimorfismo no es tan terriblemente útil aquí. Veamos otro ejemplo con el que probablemente ya estés familiarizado:

Maybe a = Just a | Nothing

Este tipo tiene dos constructores de datos. Nothing es algo aburrido, no contiene datos útiles. Por otro lado, Just contiene un valor de a , cualquiera que sea el tipo que pueda tener. Vamos a escribir una función que use este tipo, por ejemplo, obteniendo el encabezado de una lista Int , si hay alguna (espero que usted esté de acuerdo, esto es más útil que lanzar un error):

maybeHead :: [Int] -> Maybe Int maybeHead [] = Nothing maybeHead (x:_) = Just x > maybeHead [1,2,3] -- Just 1 > maybeHead [] -- None

Entonces, en este caso, a es un Int , pero funcionaría también para cualquier otro tipo. De hecho, puede hacer que nuestra función funcione para cada tipo de lista (incluso sin cambiar la implementación):

maybeHead :: [t] -> Maybe t maybeHead [] = Nothing maybeHead (x:_) = Just x

Por otro lado, puede escribir funciones que acepten solo un cierto tipo de Maybe , por ej.

doubleMaybe :: Maybe Int -> Maybe Int doubleMaybe Just x = Just (2*x) doubleMaybe Nothing= Nothing

En pocas palabras, con el polimorfismo le das a tu propio tipo la flexibilidad de 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 compañía, pero necesita tener su propio tipo Company (que contiene datos adicionales como país, dirección, cuentas atrás, etc.). Su primera implementación de Car debería cambiar para usar Company lugar de String para su primer valor. Su segunda implementación está bien, la usa como Car Company String Int y funcionaría como antes (por supuesto, las funciones que acceden a los datos de la compañía deben cambiarse).


El segundo tiene la noción de "polimorfismo" en él.

El abc puede ser de cualquier tipo. Por ejemplo, a puede ser un [String], b puede ser [Int] yc [Char]

Mientras que el primer tipo es fijo: la compañía es un String, el modelo es un String y el año es Int.

El ejemplo de Car podría no mostrar la significación de usar 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úrate de entrenar bien tu cerebro y de sentirte cómodo con esto. Es la clave para entender a Monad más tarde.


En una declaración de data , un constructor de tipo es lo que está en el lado izquierdo del signo igual. El (los) constructor (es) de datos son las cosas en el lado derecho del signo igual. Utiliza constructores de tipo 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 represente un color.

data Colour = Red | Green | Blue

Aquí, tenemos tres constructores de datos. Colour es un tipo, y el Green es un constructor que contiene un valor de tipo Colour . Del mismo modo, Red y Blue son ambos constructores que construyen valores de tipo Colour . ¡Sin embargo, podríamos imaginarlo condimentando!

data Colour = RGB Int Int Int

Todavía tenemos el tipo Colour , pero RGB no es un valor, ¡es una función que toma tres Ints y devuelve un valor! RGB tiene el tipo

RGB :: Int -> Int -> Int -> Colour

RGB es un constructor de datos que es una función que toma algunos valores como sus argumentos, y luego los utiliza 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.

Prelude> RGB 12 92 27 #0c5c1b

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 has hecho una programación previa, este concepto no debería ser muy extraño para ti.

Descanso

Si desea construir un árbol binario para almacenar String , podría imaginarse algo como

data SBTree = Leaf String | Branch String SBTree SBTree

Lo que vemos aquí es un tipo SBTree que contiene dos constructores de datos. En otras palabras, hay dos funciones (a saber, Leaf y Branch ) que construirán valores del tipo de SBTree . Si no está familiarizado con la forma en que funcionan los árboles binarios, espere allí. En realidad, no necesita saber cómo funcionan los árboles binarios, solo que este almacena String de alguna manera.

También vemos que ambos constructores de datos toman un argumento de String - 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 algo como esto:

data BBTree = Leaf Bool | Branch Bool BBTree BBTree

Constructores de tipo

Tanto SBTree como BBTree son constructores de tipos. 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 introducimos una variable de tipo a como parámetro para el constructor de tipo. En esta declaración, BTree ha 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 (ejemplos incluyen Int , [Char] y Maybe Bool ) que es un tipo que se puede asignar a un valor en su programa, y ​​una función de constructor de tipo que necesita alimentar a un escriba para poder ser asignado a un valor. Un valor nunca puede ser de tipo "lista", porque debe ser una "lista de algo ". En el mismo espíritu, un valor nunca puede ser de tipo "árbol binario", porque necesita ser un "árbol binario que almacena algo ".

Si pasamos, digamos, Bool como argumento a BTree , devuelve el tipo BTree Bool , que es un árbol binario que almacena Bool s. Reemplace cada aparición de la variable de tipo a con el tipo Bool , y puede ver por sí mismo cómo es cierto.

Si lo desea, puede ver BTree como una función del tipo

BTree :: * -> *

Las clases son algo así como tipos: el * indica un tipo concreto, por lo que decimos que BTree es de un tipo concreto a un tipo concreto.

Terminando

Retroceda 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 tipo 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 variaciones leves en nuestros valores: ponemos esas variaciones en los parámetros y dejamos que el tipo que crea el valor decida en qué argumentos van a poner. En el mismo sentido, los constructores de tipo con parámetros son geniales si queremos ligeras variaciones en nuestros tipos! Ponemos esas variaciones como parámetros y dejamos que el chico que crea el tipo decida qué argumentos van a poner.

Un caso de estudio

Como el tramo de casa aquí, podemos considerar el Maybe a tipo. Su definición es

data Maybe a = Nothing | Just a

Aquí, Maybe es 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 de Just , vemos que

Just :: a -> Maybe a

En otras palabras, Just toma un valor de tipo a y devuelve un valor de tipo Maybe a . Si miramos el tipo de Maybe , vemos que

Maybe :: * -> *

En 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 de constructor de tipo. No puede crear una lista de Maybe s - 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 Maybe es una función de constructor de tipos, pero una lista debe contener valores de un tipo concreto. Maybe Int y Maybe a sean tipos concretos (o, si lo desea, llamadas a escribir funciones de constructor que devuelvan tipos concretos).


Haskell tiene tipos de datos algebraicos , que muy pocos idiomas tienen. Esto es quizás lo que te está confundiendo.

En otros idiomas, generalmente puede hacer un "registro", "estructura" o similar, que tiene un grupo de campos con nombre que contienen varios tipos diferentes de datos. También puede hacer a veces una "enumeración", que tiene un conjunto (pequeño) de valores posibles fijos (por ejemplo, su Red , Green y Blue ).

En Haskell, puedes combinar ambos al mismo tiempo. Raro, pero cierto!

¿Por qué se llama "algebraica"? Bueno, los nerds hablan de "tipos de suma" y "tipos de productos". Por ejemplo:

data Eg1 = One Int | Two String

Un valor Eg1 es básicamente un entero o una cadena. Por lo tanto, el conjunto de todos los valores Eg1 posibles es la "suma" del conjunto de todos los valores enteros posibles y todos los posibles valores de cadena. Por lo tanto, los nerds se refieren a Eg1 como un "tipo de suma". Por otra parte:

data Eg2 = Pair Int String

Cada valor de Eg2 consiste en un número entero y una cadena. Entonces, el conjunto de todos los valores posibles de Eg2 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 das a un constructor múltiples campos para hacer un tipo de producto, y tienes varios constructores para hacer una suma (de productos).

Como ejemplo de por qué podría ser útil, supongamos que tiene algo que genera datos como XML o JSON, y requiere un registro de configuración, pero obviamente, los ajustes de configuración para XML y para JSON son totalmente diferentes. Entonces puedes hacer algo como esto:

data Config = XML_Config {...} | JSON_Config {...}

(Con algunos campos adecuados ahí, obviamente.) No puede hacer cosas como esta en los lenguajes de programación normales, por lo que la mayoría de la gente no está acostumbrada.


Se trata de tipos : en el primer caso, configura los tipos String (para empresa y modelo) e Int para año. En el segundo caso, eres más genérico. a , b y c pueden ser los mismos tipos que en el primer ejemplo, o algo completamente diferente. Por ejemplo, puede ser útil dar el año como una cadena en lugar de un número entero. Y si lo desea, incluso puede usar su tipo de Color .