Tipos opacos são uma técnica de encapsulamento em Elm.
Algumas pessoas também chamam isso de ocultação de informação, ou ocultação de detalhes de implementação.
Eles também são muito usados para garantir a validação, usando o padrão Parse, dont validate.
Por exemplo, você pode ter um tipo opaco EmailAddress, que só pode conter endereços de e-mail válidos (ao contrário de uma String, que pode conter qualquer coisa).
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. Essa é a única forma de criar tipos personalizados. O compilador também permite a desestruturação dessas 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 padrão, tudo isso é interno ao arquivo / módulo, e usamos a palavra-chave exposing para escolher o que exportar.
O módulo Maybe expõe o tipo Maybe (para que outros módulos possam usar valores Maybe existentes), e também expõe as variantes Nothing e Just (para que outros módulos possam criar e desestruturar valores Maybe).
Ele usa a seguinte sintaxe para fazer isso.
A parte Maybe(..) diz ao Elm para expor todas as variantes, além da definição do tipo personalizado.
module Maybe exposing
( Maybe(..)
-- ...
)
Para criar um tipo opaco, basta escolher não exportar as variantes.
Para fazer isso com o tipo Maybe, você usaria a seguinte sintaxe:
module Maybe exposing
( Maybe
-- ...
)
Quando você faz isso, quase sempre precisa fornecer outra forma de criar e obter as variantes, expondo funções personalizadas. Essas funções personalizadas permitem aplicar alguma lógica ao criar e obter as variantes. Isso esconde os detalhes internos de implementação do módulo e significa que você pode mudar os detalhes de implementação o quanto quiser, desde que as funções expostas continuem as mesmas. Também permite que você mantenha invariantes ao criar e usar o tipo.
Provavelmente o uso mais comum dos tipos opacos é garantir a validação, usando o padrão Parse, dont validate.
Nesse padrão, você expõe uma função que recebe uma entrada com menos estrutura / segurança de tipos do que você quer, como uma String, e então a interpreta em um tipo mais estruturado, como EmailAddress, retornando o tipo ou retornando um erro (geralmente Maybe ou Result).
Por exemplo, no código abaixo, só é possível que módulos externos criem um tipo EmailAddress usando a função parseEmailAddress, então quando você está trabalhando com o tipo EmailAddress, você tem certeza de que ele é 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
-- ...