Los tipos opacos son una técnica de encapsulación en Elm.
Algunas personas también llaman a esto ocultamiento de información, u ocultar los detalles de implementación.
También se usan comúnmente para forzar la validación, usando el patrón Parse, dont validate.
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. Estas son 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 _'
Por defecto, todo esto es interno del archivo o módulo, y usamos la palabra clave exposing para elegir qué exportar.
Para crear un tipo opaco, simplemente eliges no exportar las variantes.
Cuando haces esto, casi siempre necesitas proporcionar 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 forzar la validación, usando el patrón Parse, dont validate.
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 parseas a 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, solo es posible que los módulos externos creen 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 (¡y hasta mágico!) para guardar un tesoro.
Los estudiantes con buen ojo habrán notado que el anterior TreasureChest no era tan seguro.
Si usabas la función getTreasure, tenías que proporcionar la contraseña, pero igual podías simplemente desestructurar el tipo TreasureChest para obtener el tesoro, sin tener que saber la contraseña.
Y lo que es peor, ¡incluso podías obtener la contraseña de la misma forma!
Vamos a arreglarlo usando un tipo opaco.
Define un tipo personalizado Password con una sola variante.
La variante debe tener un valor String asociado para la contraseña.
Debes exponer el tipo Password, pero no la variante.
Esto hace que Password sea un tipo opaco, y significa que ya no puedes usar 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 anotación de tipo, que tome un String y devuelva un Maybe Password.
Owasp recomienda que las contraseñas tengan al menos 8 caracteres, así que usemos el patrón Parse, don't validate para hacer cumplir esta invariante.
Si la contraseña tiene menos de 8 caracteres, createPassword debe devolver Nothing; de lo contrario, debe devolver Just Password.
Debes exponer el tipo SecureTreasureChest, pero no la variante.
Esto hace que SecureTreasureChest sea un tipo opaco, y significa que ya no puedes usar desestructuración para obtener la contraseña ni el tesoro.
Esta función debe tomar dos parámetros y tener una anotació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 anotación de tipo.
String (para la contraseña)SecureTreasureChest
Esta función debe comprobar el intento de contraseña proporcionado contra el Password que está en el 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.