Os tipos opacos são uma técnica de encapsulamento em Elm.
Há também quem lhe chame ocultação de informação, ou ocultação dos detalhes de implementação.
São também muito usados para impor validação, seguindo 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 fazer 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.
O módulo Maybe expõe o tipo Maybe (para que outros módulos possam usar valores Maybe já existentes) e também expõe as variantes Nothing e Just (para que outros módulos possam criar e desestruturar valores Maybe).
Para isso, usa a seguinte sintaxe.
A parte Maybe(..) diz ao Elm para expor todas as variantes, bem como a definição do tipo personalizado.
module Maybe exposing
( Maybe(..)
-- ...
)
Para criar um tipo opaco, basta não exportar as variantes.
Para fazer isto com o tipo Maybe, usarias a seguinte sintaxe:
module Maybe exposing
( Maybe
-- ...
)
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 esconde 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. Também te permite manter os invariantes ao criar e usar o tipo.
Provavelmente, a utilização mais comum dos tipos opacos é impor validação, seguindo o padrão Parse, dont validate.
Neste padrão, expões uma função que recebe um valor de entrada com menos estrutura / segurança de tipos do que aquela que queres, como uma String, e depois convertes isso num tipo mais estruturado, como EmailAddress, devolvendo o tipo ou devolvendo um erro (normalmente Maybe ou Result).
Por exemplo, no código abaixo, os módulos externos só conseguem criar um tipo EmailAddress através da função parseEmailAddress. Por isso, quando estás a trabalhar com o tipo EmailAddress, tens a 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
-- ...