Los tipos opacos son una técnica de encapsulación en Elm.
Algunas personas también lo llaman ocultación de información, u ocultar los detalles de implementación.
También se usan habitualmente para imponer la validación, mediante el patrón Analiza, no valides.
Por ejemplo, podrías tener un tipo opaco EmailAddress, que solo puede contener direcciones de correo electrónico válidas (a diferencia de un String, que puede contener cualquier cosa).
Los tipos personalizados tienen una o más variantes, y el compilador genera automáticamente una función para crear cada variante, usando el nombre de la variante. Esta es la única forma en que se pueden crear los tipos personalizados. El compilador también permite la desestructuración de estas variantes.
type Maybe a
= Nothing --> Variant implies a 'Nothing' function, with type 'Maybe a', and destructuring of 'Nothing'
| Just a --> Variant implies a 'Just' function, with type 'a -> Maybe a', and destructuring of 'Just _'
De forma predeterminada, todo esto es interno al archivo o módulo, y usamos la palabra clave exposing para elegir qué exportar.
Para crear un tipo opaco, basta con que decidas no exportar las variantes.
Cuando haces esto, casi siempre necesitas ofrecer otra forma de crear y obtener las variantes, exponiendo funciones personalizadas. Estas funciones personalizadas te permiten aplicar cierta lógica al crear y obtener las variantes. Esto oculta los detalles de implementación internos del módulo y significa que puedes cambiar los detalles de implementación tanto como quieras, siempre que las funciones expuestas sigan siendo las mismas.
Probablemente, el uso más común de los tipos opacos sea imponer la validación, mediante el patrón Analiza, no valides.
En este patrón, expones una función que recibe una entrada con menos estructura o seguridad de tipos de la que quieres, como un String, y luego la analizas para convertirla en un tipo más estructurado, como EmailAddress, ya sea devolviendo el tipo o devolviendo un error (normalmente Maybe o Result).
Por ejemplo, en el código de abajo, los módulos externos solo pueden crear un tipo EmailAddress usando la función parseEmailAddress, así que cuando trabajas con el tipo EmailAddress tienes la certeza de que es válido.
module EmailAddress exposing ( EmailAddress, parseEmailAddress, getEmailAddress )
-- The `EmailAddress` custom type has one variant, also called `EmailAddress`
-- When a custom type has one variant, it is idiomatic to use the same name for both.
type EmailAddress =
EmailAddress String
-- create an EmailAddress variant
parseEmailAddress: String -> Maybe EmailAddress
parseEmailAddress candidateEmailAddress =
if isValidEmailAddress candidateEmailAddress then
Just (EmailAddress candidateEmailAddress)
else
Nothing
-- get an EmailAddress variant
getEmailAddress: EmailAddress -> String
getEmailAddress ( EmailAddress emailAdress ) =
emailAdress
isValidEmailAddress: String -> bool
-- ...
En este ejercicio vas a escribir un SecureTreasureChest genérico (¡o mágico!) para guardar un tesoro.
Los estudiantes con buen ojo habrán notado que el TreasureChest anterior no era tan seguro.
Si usabas la función getTreasure, tenías que proporcionar la contraseña, pero aun así podías simplemente desestructurar el tipo TreasureChest para obtener el tesoro, sin necesidad de conocer la contraseña.
Y lo que es peor, ¡incluso podías recuperar la contraseña de la misma forma!
Vamos a arreglarlo usando un tipo opaco.
Define un tipo personalizado Password con una única variante.
La variante debe tener un valor String asociado, para la contraseña.
Debes exponer el tipo Password, pero no la variante.
Esto convierte Password en un tipo opaco, y significa que ya no puedes usar la desestructuración para obtener la contraseña.
No es posible comprobar si un tipo es opaco o no; en código de producción, usarías elm-review (una herramienta de análisis estático) para hacerlo cumplir.
Define y expón una función createPassword, con una definición de tipo, que toma un String y devuelve un Maybe Password.
Owasp recomienda que las contraseñas tengan al menos 8 caracteres, así que vamos a usar el patrón «Parse, don't validate» para hacer cumplir este invariante.
Si la contraseña tiene menos de 8 caracteres, createPassword debe devolver Nothing; en caso contrario, debe devolver Just Password.
Debes exponer el tipo SecureTreasureChest, pero no la variante.
Esto convierte SecureTreasureChest en un tipo opaco, y significa que ya no puedes usar la desestructuración para obtener la contraseña ni el tesoro.
Esta función debe tomar dos parámetros y tener una definición de tipo.
Password (para la contraseña)La función debe devolver un SecureTreasureChest.
Expón esta función, para que otros módulos puedan crear instancias de SecureTreasureChest.
Esta función debe tomar dos parámetros y tener una definición de tipo.
String (para la contraseña)SecureTreasureChest
Esta función debe comprobar el intento de contraseña proporcionado contra el Password del SecureTreasureChest.
La función debe devolver un Maybe.
Si las contraseñas coinciden, devuelve Just con el valor genérico del SecureTreasureChest (¡el tesoro!).
Si las contraseñas no coinciden, devuelve Nothing.
Expón esta función, para que otros módulos puedan obtener el tesoro de un SecureTreasureChest, siempre que proporcionen la contraseña correcta.
Regístrate en Exercism para aprender y dominar Elm con 28 conceptos110 ejercicios y mentoría humana real, todo gratis.