Vamos a hacer un recorrido a través de distintos tipos y formas de utilizarlos, particularmente en Haskell.
Nuestro invitado especial de hoy: las listas.
import GHC.Types
:i List
listaVacia = []
listaVacia
[]
listaNoVacia = [1, 2, 3, 4, 5]
listaNoVacia
[1,2,3,4,5]
otraListaNoVacia = (1 : (2 : (3 : (4 : (5 : [])))))
otraListaNoVacia
[1,2,3,4,5]
Tipos con variables de tipos, es decir, que pueden reemplazarse por cualquier otro tipo.
Spoiler: Esto tiene que ver con la lógica de segundo orden (pero eso viene más adelante).
:i Maybe
ejemploJust :: Maybe String
ejemploJust = Just "Un valor"
ejemploNothing :: Maybe String
ejemploNothing = Nothing
ejemploJust
Just "Un valor"
ejemploNothing
Nothing
agregarHola :: Maybe String -> Maybe String
agregarHola (Just x) = Just (x ++ " Hola")
agregarHola Nothing = Nothing
agregarHola (Just "Mundo")
agregarHola Nothing
Just "Mundo Hola"
Nothing
:i (,)
flip :: (a, b) -> (b, a)
flip (x, y) = (y, x)
flip (1, 2)
flip (['a'], Just 1)
(2,1)
(Just 1,"a")
En Haskell definimos tipos de datos algebráicos con data
.
-- Define las listas usando tipos de datos algebráicos
data Lista a = Vacia | Cons a (Lista a) deriving Show
Vacia
Cons 1 (Cons 2 (Cons 3 Vacia))
Vacia
Cons 1 (Cons 2 (Cons 3 Vacia))
Por ejemplo, ¿cómo definiríamos un tipo de dato para una lista no vacía?
type ListaNoVacia a = [a]
:i ListaNoVacia
ejemplo :: ListaNoVacia Int
ejemplo = []
ejemplo
[]
:t ejemplo
module ListaNoVacia (buildListaNoVacia)
newtype ListaNoVacia a = NoVacia [a]
newtype ListaNoVacia a = NoVacia [a] deriving Show
buildListaNoVacia :: a -> [a] -> ListaNoVacia a
buildListaNoVacia x xs = NoVacia (x : xs)
buildListaNoVacia 1 []
NoVacia [1]
El tipo de dato únicamente se puede construir si existen las condiciones necesarias para cumplir las garantías que resultan de interés.
-- Definición del tipo de dato
data ListaNoVacia' a = Unitario a | ConsNoVacia a (ListaNoVacia' a) deriving Show
-- Ejemplo con el tipo de dato
ConsNoVacia 1 (ConsNoVacia 2 (Unitario 3))
Unitario 10
ConsNoVacia 1 (ConsNoVacia 2 (Unitario 3))
Unitario 10
Desventajas:
Ghost types
Damos dos componentes: un objeto, y un parámetro para indicar las propiedades del objeto.
newtype Lista a t = Lista [a] deriving Show
data Vacia
data NoVacia
otraListaVacia :: Lista Int Vacia
otraListaVacia = Lista []
otraListaNoVacia :: Lista Int NoVacia
otraListaNoVacia = Lista [1, 2, 3]
:i otraListaVacia
:i otraListaNoVacia
otraListaVacia
Lista []
otraListaNoVacia
Lista [1,2,3]
Ok. Pero, ¿qué nos detiene de hacer lo siguiente?
listaMala :: Lista Int Vacia
listaMala = Lista [1, 2, 3, 4, 5]
listaMala
Lista [1,2,3,4,5]
:i listaMala
Spoilers ;)
newtype Temperatura a = Temp Double deriving Show
data Celsius
data Fahrenheit
celsiusToF :: Temperatura Celsius -> Temperatura Fahrenheit
celsiusToF (Temp c) = Temp ((c * 1.8) + 32)
fahrenheitToC :: Temperatura Fahrenheit -> Temperatura Celsius
fahrenheitToC (Temp f) = Temp ((f - 32) / 1.8)
celsiusToF (Temp 20 :: Temperatura Celsius)
Temp 68.0
fahrenheitToC (Temp 77 :: Temperatura Fahrenheit)
Temp 25.0
celsiusToF (Temp 90 :: Temperatura Fahrenheit)
<interactive>:1:13: error: [GHC-83865] • Couldn't match type ‘Fahrenheit’ with ‘Celsius’ Expected: Temperatura Celsius Actual: Temperatura Fahrenheit • In the first argument of ‘celsiusToF’, namely ‘(Temp 90 :: Temperatura Fahrenheit)’ In the expression: celsiusToF (Temp 90 :: Temperatura Fahrenheit) In an equation for ‘it’: it = celsiusToF (Temp 90 :: Temperatura Fahrenheit)