Tipos¶

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.

Listas¶

In [1]:
import GHC.Types

:i List
type List :: * -> *
data List a = [] | a : [a]
-- Defined in ‘GHC.Types’
instance Monoid [a] -- Defined in ‘GHC.Base’
instance Semigroup [a] -- Defined in ‘GHC.Base’
instance Foldable [] -- Defined in ‘Data.Foldable’
instance Traversable [] -- Defined in ‘Data.Traversable’
instance Read a => Read [a] -- Defined in ‘GHC.Read’
instance Show a => Show [a] -- Defined in ‘GHC.Show’
instance Applicative [] -- Defined in ‘GHC.Base’
instance Functor [] -- Defined in ‘GHC.Base’
instance MonadFail [] -- Defined in ‘Control.Monad.Fail’
instance Monad [] -- Defined in ‘GHC.Base’
instance Eq a => Eq [a] -- Defined in ‘GHC.Classes’
instance Ord a => Ord [a] -- Defined in ‘GHC.Classes’
In [2]:
listaVacia = []

listaVacia
[]
In [3]:
listaNoVacia = [1, 2, 3, 4, 5]

listaNoVacia
[1,2,3,4,5]
In [4]:
otraListaNoVacia = (1 : (2 : (3 : (4 : (5 : [])))))

otraListaNoVacia
Redundant bracket
Found:
(1 : (2 : (3 : (4 : (5 : [])))))
Why Not:
1 : (2 : (3 : (4 : (5 : []))))
Use list literal
Found:
1 : (2 : (3 : (4 : (5 : []))))
Why Not:
[1, 2, 3, 4, 5]
[1,2,3,4,5]

Tipos Polimórficos¶

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).

In [5]:
:i Maybe
type Maybe :: * -> *
data Maybe a = Nothing | Just a
-- Defined in ‘GHC.Maybe’
instance Traversable Maybe -- Defined in ‘Data.Traversable’
instance MonadFail Maybe -- Defined in ‘Control.Monad.Fail’
instance Applicative Maybe -- Defined in ‘GHC.Base’
instance Foldable Maybe -- Defined in ‘Data.Foldable’
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Monad Maybe -- Defined in ‘GHC.Base’
instance Semigroup a => Monoid (Maybe a) -- Defined in ‘GHC.Base’
instance Semigroup a => Semigroup (Maybe a) -- Defined in ‘GHC.Base’
instance Read a => Read (Maybe a) -- Defined in ‘GHC.Read’
instance Eq a => Eq (Maybe a) -- Defined in ‘GHC.Maybe’
instance Ord a => Ord (Maybe a) -- Defined in ‘GHC.Maybe’
instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
In [7]:
ejemploJust :: Maybe String
ejemploJust = Just "Un valor"

ejemploNothing :: Maybe String
ejemploNothing = Nothing
In [8]:
ejemploJust
Just "Un valor"
In [9]:
ejemploNothing
Nothing
In [14]:
agregarHola :: Maybe String -> Maybe String
agregarHola (Just x) = Just (x ++ " Hola")
agregarHola Nothing = Nothing
In [15]:
agregarHola (Just "Mundo")
agregarHola Nothing
Just "Mundo Hola"
Nothing
In [16]:
:i (,)
type (,) :: * -> * -> *
data (,) a b = (,) a b
-- Defined in ‘GHC.Tuple.Prim’
instance Foldable ((,) a) -- Defined in ‘Data.Foldable’
instance Traversable ((,) a) -- Defined in ‘Data.Traversable’
instance (Bounded a, Bounded b) => Bounded (a, b) -- Defined in ‘GHC.Enum’
instance (Read a, Read b) => Read (a, b) -- Defined in ‘GHC.Read’
instance (Eq a, Eq b) => Eq (a, b) -- Defined in ‘GHC.Classes’
instance (Ord a, Ord b) => Ord (a, b) -- Defined in ‘GHC.Classes’
instance Monoid a => Applicative ((,) a) -- Defined in ‘GHC.Base’
instance Functor ((,) a) -- Defined in ‘GHC.Base’
instance Monoid a => Monad ((,) a) -- Defined in ‘GHC.Base’
instance (Monoid a, Monoid b) => Monoid (a, b) -- Defined in ‘GHC.Base’
instance (Semigroup a, Semigroup b) => Semigroup (a, b) -- Defined in ‘GHC.Base’
instance (Show a, Show b) => Show (a, b) -- Defined in ‘GHC.Show’
In [17]:
flip :: (a, b) -> (b, a)
flip (x, y) = (y, x)
In [19]:
flip (1, 2)
flip (['a'], Just 1)
(2,1)
(Just 1,"a")

Tipos de Datos Algebráicos¶

Tipo suma: a | b | ... | z

Tipo producto: a b ... z

En Haskell definimos tipos de datos algebráicos con data.

In [21]:
-- Define las listas usando tipos de datos algebráicos
data Lista a = Vacia | Cons a (Lista a) deriving Show
In [23]:
Vacia

Cons 1 (Cons 2 (Cons 3 Vacia))
Vacia
Cons 1 (Cons 2 (Cons 3 Vacia))

Seguridad¶

  • Tener garantías al momento de compilación.
  • Prevenir errores durante la ejecución.
  • Expresar el contexto del negocio.

Por ejemplo, ¿cómo definiríamos un tipo de dato para una lista no vacía?

Solución de Principiantes¶

  • No dar garantías en los tipos.
  • Verificar que las listas no sean vacías en tiempo de ejecución.
In [25]:
type ListaNoVacia a = [a]
In [26]:
:i ListaNoVacia
type ListaNoVacia :: * -> *
type ListaNoVacia a = [a]
-- Defined at :1:1
In [27]:
ejemplo :: ListaNoVacia Int
ejemplo = []
In [28]:
ejemplo
[]
In [29]:
:t ejemplo
ejemplo :: ListaNoVacia Int

Solución Intermedia (por oscuridad)¶

  • No permitimos que el usuario defina sus datos.
  • Verificamos que el dato se pueda construir en tiempo de ejecución.
  • Únicamente si cumple nuestras condiciones, permitimos su construcción.
module ListaNoVacia (buildListaNoVacia)

newtype ListaNoVacia a = NoVacia [a]
In [30]:
newtype ListaNoVacia a = NoVacia [a] deriving Show
In [31]:
buildListaNoVacia :: a -> [a] -> ListaNoVacia a
buildListaNoVacia x xs = NoVacia (x : xs)
In [32]:
buildListaNoVacia 1 []
NoVacia [1]

Solución Experta (tipos de datos algebráicos)¶

El tipo de dato únicamente se puede construir si existen las condiciones necesarias para cumplir las garantías que resultan de interés.

In [35]:
-- Definición del tipo de dato
data ListaNoVacia' a = Unitario a | ConsNoVacia a (ListaNoVacia' a) deriving Show
In [37]:
-- Ejemplo con el tipo de dato
ConsNoVacia 1 (ConsNoVacia 2 (Unitario 3))

Unitario 10
ConsNoVacia 1 (ConsNoVacia 2 (Unitario 3))
Unitario 10

Desventajas:

  • Requiere una construcción extra encima de las listas.
  • No aprovecha las optimizaciones que tienen las listas en el compilador.
  • Ocupa más memoria estar cargando con la información del tipo de dato.

Solución Nivel Héroe (tipos fantasma)¶

Ghost types

Damos dos componentes: un objeto, y un parámetro para indicar las propiedades del objeto.

In [38]:
newtype Lista a t = Lista [a] deriving Show
In [39]:
data Vacia
data NoVacia
In [40]:
otraListaVacia :: Lista Int Vacia
otraListaVacia = Lista []

otraListaNoVacia :: Lista Int NoVacia
otraListaNoVacia = Lista [1, 2, 3]
In [41]:
:i otraListaVacia
:i otraListaNoVacia
otraListaVacia :: Lista Int Vacia -- Defined at :2:1
otraListaNoVacia :: Lista Int NoVacia -- Defined at :4:1
In [42]:
otraListaVacia
Lista []
In [43]:
otraListaNoVacia
Lista [1,2,3]

Ok. Pero, ¿qué nos detiene de hacer lo siguiente?

In [44]:
listaMala :: Lista Int Vacia
listaMala = Lista [1, 2, 3, 4, 5]
In [45]:
listaMala
Lista [1,2,3,4,5]
In [46]:
:i listaMala
listaMala :: Lista Int Vacia -- Defined at :2:1

Spoilers ;)

¿Utilidad?¶

In [48]:
newtype Temperatura a = Temp Double deriving Show

data Celsius
data Fahrenheit
In [49]:
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)
In [50]:
celsiusToF (Temp 20 :: Temperatura Celsius)
Temp 68.0
In [51]:
fahrenheitToC (Temp 77 :: Temperatura Fahrenheit)
Temp 25.0
In [ ]:
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)
In [ ]: