Os tipos opacos são uma técnica de encapsulamento em Elm.
Há quem lhe chame também ocultação de informação, ou ocultação de detalhes de implementação.
Também são comummente usados para impor validação, com o padrão Parse, dont validate.
Por exemplo, podes ter um tipo opaco EmailAddress, que só pode conter endereços de email válidos (ao contrário de uma String, que pode conter qualquer coisa).
Os tipos personalizados têm uma ou mais variantes, e o compilador gera automaticamente uma função para criar cada variante, usando o nome da variante. São a única forma de criar tipos personalizados. O compilador também permite a desestruturação destas 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 predefinição, tudo isto é interno ao ficheiro / módulo, e usamos a palavra-chave exposing para escolher o que exportar.
Para criar um tipo opaco, basta escolheres não exportar as variantes.
Quando fazes isto, quase sempre precisas de arranjar outra forma de criar e obter as variantes, expondo funções personalizadas. Estas funções personalizadas permitem-te aplicar alguma lógica ao criar e obter as variantes. Isto oculta os detalhes de implementação internos do módulo, e significa que podes alterar os detalhes de implementação tanto quanto quiseres, desde que as funções expostas se mantenham iguais.
Provavelmente, a utilização mais comum dos tipos opacos é impor validação, com o padrão Parse, dont validate.
Neste padrão, expões uma função que recebe uma entrada com menos estrutura / segurança de tipos do que queres, como uma String, e depois analisas isso para obter um tipo mais estruturado, como EmailAddress, devolvendo o tipo ou devolvendo um erro (normalmente Maybe ou Result).
Por exemplo, no código abaixo, só é possível aos módulos externos criar um tipo EmailAddress com a função parseEmailAddress, por isso, quando estás a trabalhar com o tipo EmailAddress, tens a certeza de que é 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
-- ...
Neste exercício vais escrever um SecureTreasureChest genérico (e mágico!) para guardar um tesouro.
Os alunos mais atentos já devem ter reparado que o TreasureChest anterior não era assim tão seguro!
Se usavas a função getTreasure, tinhas de fornecer a palavra-passe, mas mesmo assim podias simplesmente desestruturar o tipo TreasureChest para obter o tesouro, sem precisares de saber a palavra-passe.
Pior ainda: também podias obter a palavra-passe da mesma forma!
Vamos corrigir isso com um tipo opaco.
Define um tipo personalizado Password com uma única variante.
A variante deve ter um valor String associado, para a palavra-passe.
Deves expor o tipo Password, mas não a variante.
Isto torna Password um tipo opaco e significa que já não podes usar desestruturação para obter a palavra-passe.
Não é possível testar se um tipo é opaco ou não; em código de produção, usarias o elm-review (uma ferramenta de análise estática) para o garantir.
Define e expõe uma função createPassword, com uma definição de tipo, que recebe uma String e devolve um Maybe Password.
A Owasp recomenda que as palavras-passe tenham pelo menos 8 carateres, por isso vamos usar o padrão Parse, don't validate para impor este invariante.
Se a palavra-passe tiver menos de 8 carateres, createPassword deve devolver Nothing; caso contrário, deve devolver Just Password.
Deves expor o tipo SecureTreasureChest, mas não a variante.
Isto torna SecureTreasureChest um tipo opaco e significa que já não podes usar desestruturação para obter a palavra-passe nem o tesouro.
Esta função deve receber dois parâmetros e ter uma definição de tipo.
Password (para a palavra-passe)A função deve devolver um SecureTreasureChest.
Expõe esta função, para que outros módulos possam criar instâncias de SecureTreasureChest.
Esta função deve receber dois parâmetros e ter uma definição de tipo.
String (para a palavra-passe)SecureTreasureChest
Esta função deve verificar a tentativa de palavra-passe fornecida contra a Password no SecureTreasureChest.
A função deve devolver um Maybe.
Se as palavras-passe coincidirem, devolve Just com o valor genérico do SecureTreasureChest (o tesouro!)
Se as palavras-passe não coincidirem, devolve Nothing.
Expõe esta função, para que outros módulos possam obter o tesouro de um SecureTreasureChest, desde que forneçam a palavra-passe correta.
Inscreve-te no Exercism para aprenderes e dominares Elm com 28 conceitos110 exercícios, e mentoria humana real, tudo grátis.