Nota: Los tipos personalizados solían llamarse “tipos de unión” en Elm. En otras partes puede que los hayas oído llamar como “unión etiquetada” o “tipos de datos algebraicos”, también.

Tipos personalizados

Hasta ahora hemos visto muchos tipos, como Bool, Int y String. Pero ¿cómo definimos nuestro propio tipo?

Imaginemos que queremos crear un chat. Cada usuario necesita un nombre, pero tal vez algunos usuarios no tienen cuenta permanente, solamente escriben un nombre cada vez que entran.

Podemos describir esta situación definiendo un tipo UserStatus que lista todas las posibilidades:

type UserStatus
    = Regular
    | Visitor

El tipo UserStatus tiene dos variantes. Una persona puede ser Regular o Visitor. Así que representamos un usuario usando un registro así:

type UserStatus
    = Regular
    | Visitor


type alias User =
    { status : UserStatus
    , name : String
    }


thomas =
    { status = Regular, name = "Thomas" }


kate95 =
    { status = Visitor, name = "kate95" }

Ahora podemos llevar cuenta de si un usuario es Regular, o sea que tiene cuenta, o es Visitor, un visitante temporal. No está tan mal, pero podemos simplificarlo aún más.

En vez de crear un tipo personalizado y un alias de tipo, podemos representar todo esto con un sólo tipo personalizado. Las variantes Regular y Visitor pueden llevar información asociada. En nuestro caso, la información es un valor String:

type User
    = Regular String
    | Visitor String


thomas =
    Regular "Thomas"


kate95 =
    Visitor "kate95"

La información se asocia directamente a la variante, y ya no hay necesidad de definir un registro.

Otro beneficio de esta manera de hacerlo es que cada variante puede tener distinta información asociada. Por ejemplo, los usuarios Regular podrían informar su edad al registrarse. No hay una buena manera de capturar eso usando registros, pero cuando definimos un tipo personalizado, ya no es problema. Añadamos un poco de información a la variante Regular en un ejemplo interactivo:

[ { "add-type": "User", "input": "type User\n = Regular String Int\n | Visitor String\n" }, { "input": "Regular", "value": "\u001b[36m<function>\u001b[0m", "type_": "String -> Int -> User" }, { "input": "Visitor", "value": "\u001b[36m<function>\u001b[0m", "type_": "String -> User" }, { "input": "Regular \"Thomas\" 44", "value": "\u001b[96mRegular\u001b[0m \u001b[93m\"Thomas\"\u001b[0m \u001b[95m44\u001b[0m", "type_": "User" }, { "input": "Visitor \"kate95\"", "value": "\u001b[96mVisitor\u001b[0m \u001b[93m\"kate95\"\u001b[0m", "type_": "User" } ]

Prueba definir un usuario Regular con nombre y edad ⬆️

Sólo hemos añadido la edad, pero cada variante en un tipo puede diverger mucho más. Por ejemplo, podríamos querer añadir información de ubicación a los usuarios Regular, para ofrecer chats regionales; basta con agregar esa información a su variante. O tal vez queremos tener usuarios anónimos también, y para eso podemos añadir una variante Anonymous.

type User
    = Regular String Int Location
    | Visitor String
    | Anonymous

¡Así de fácil! Veamos más ejemplos.

Mensajes

En la sección de arquitectura vimos algunos ejemplos donde definimos un tipo Msg. Usar tipos personalizados es la manera más usual y recomendada de definir los mensajes. En nuestro chat, tal vez necesitemos un tipo Msg como este:

type Msg
    = PressedEnter
    | ChangedDraft String
    | ReceivedMessage { user : User, message : String }
    | ClickedExit

Tenemos cuatro variantes. Algunas variantes no llevan información asociada, otras llevan bastante. Fíjate en que ReceivedMessage lleva un registro asociado. Es totalmente normal; cualquier tipo puede ser parte de esa información asociada. Esto te permite describir interacciones en tu aplicación en forma muy precisa.

Modelado

Los tipos personalizados se vuelven muy poderosos cuando empiezas a modelar situaciones en forma precisa. Por ejemplo, si estás esperando que se carguen ciertos datos, puedes querer un modelo con un tipo personalizado como este:

type Profile
    = Failure
    | Loading
    | Success { name : String, description : String }

Empiezas en el estado Loading, y luego haces la transición a Failure o Success según lo que ocurra. Esto permite que sea muy fácil escribir una función view que siempre muestre algo sensato mientras se cargan los datos.

¡Ya aprendimos a crear tipos personalizados! En la próxima sección vamos a ver cómo usarlos.

Nota: Los tipos personalizados son la funcionalidad más importante de Elm. Tienen muchísima profundidad, especialmente cuando entres en el hábito de modelar estados en forma bien precisa. Escribí los apéndices “Tipos como conjuntos” y “Tipos como bits” para tratar de comunicar esta profundidad. Ojalá que los encuentres interesantes.

results matching ""

    No results matching ""