Na verdade, é apenas um construtor de dados normal que é definido no Prelude , que é a biblioteca padrão que é importada automaticamente para cada módulo.
O que talvez seja, estruturalmente
A definição é mais ou menos assim:
data Maybe a = Just a
| Nothing
Essa declaração define um tipo,, Maybe a
que é parametrizado por uma variável de tipo a
, o que significa apenas que você pode usá-lo com qualquer tipo no lugar de a
.
Construindo e Destruindo
O tipo possui dois construtores, Just a
e Nothing
. Quando um tipo tem vários construtores, significa que um valor do tipo deve ter sido construído com apenas um dos construtores possíveis. Para este tipo, um valor foi construído por meio de Just
ou Nothing
, não há outras possibilidades (sem erro).
Como Nothing
não tem tipo de parâmetro, quando é usado como construtor, ele nomeia um valor constante que é membro do tipo Maybe a
para todos os tipos a
. Mas o Just
construtor tem um parâmetro de tipo, o que significa que quando usado como um construtor ele atua como uma função de tipo a
para Maybe a
, ou seja, tem o tipoa -> Maybe a
Portanto, os construtores de um tipo constroem um valor desse tipo; o outro lado das coisas é quando você gostaria de usar esse valor, e é aí que a correspondência de padrões entra em jogo. Ao contrário das funções, os construtores podem ser usados em expressões de vinculação de padrão e esta é a maneira pela qual você pode fazer a análise de caso de valores que pertencem a tipos com mais de um construtor.
Para usar um Maybe a
valor em uma correspondência de padrão, você precisa fornecer um padrão para cada construtor, assim:
case maybeVal of
Nothing -> "There is nothing!"
Just val -> "There is a value, and it is " ++ (show val)
Nesse caso, expressão, o primeiro padrão corresponderia se o valor fosse Nothing
, e o segundo corresponderia se o valor fosse construído com Just
. Se o segundo corresponder, ele também vincula o nome val
ao parâmetro que foi passado ao Just
construtor quando o valor com o qual você está correspondendo foi construído.
O que talvez signifique
Talvez você já saiba como isso funciona; não há realmente nenhuma mágica para os Maybe
valores, é apenas um tipo de dados algébrico Haskell (ADT) normal. Mas é bastante usado porque efetivamente "levanta" ou estende um tipo, como Integer
no seu exemplo, para um novo contexto no qual ele tem um valor extra ( Nothing
) que representa uma falta de valor! O sistema de tipo então requer que você verifique esse valor extra antes de permitir que você obtenha o Integer
que pode estar lá. Isso evita um número notável de bugs.
Muitas linguagens hoje lidam com esse tipo de valor "sem valor" por meio de referências NULL. Tony Hoare, um eminente cientista da computação (ele inventou o Quicksort e é um vencedor do Prêmio Turing), reconhece isso como seu "erro de um bilhão de dólares" . O tipo Maybe não é a única maneira de consertar isso, mas provou ser uma maneira eficaz de fazer isso.
Talvez como um Functor
A ideia de transformar um tipo em outro de modo que as operações no tipo antigo também possam ser transformadas para funcionar no novo tipo é o conceito por trás da classe de tipo Haskell chamada Functor
, que Maybe a
tem uma instância útil de.
Functor
fornece um método chamado fmap
, que mapeia funções que variam de valores do tipo base (como Integer
) para funções que variam de valores do tipo levantado (como Maybe Integer
). Uma função transformada com fmap
para trabalhar em um Maybe
valor funciona assim:
case maybeVal of
Nothing -> Nothing -- there is nothing, so just return Nothing
Just val -> Just (f val) -- there is a value, so apply the function to it
Portanto, se você tem um Maybe Integer
valor m_x
e uma Int -> Int
função f
, pode fmap f m_x
aplicar a função f
diretamente ao Maybe Integer
sem se preocupar se ela realmente tem um valor ou não. Na verdade, você poderia aplicar uma cadeia inteira de Integer -> Integer
funções elevadas aos Maybe Integer
valores e apenas se preocupar em verificar explicitamente uma Nothing
vez quando terminar.
Talvez como uma mônada
Não tenho certeza de como você está familiarizado com o conceito de a Monad
ainda, mas pelo menos você já usou IO a
antes, e a assinatura de tipo IO a
é notavelmente semelhante a Maybe a
. Embora IO
seja especial por não expor seus construtores para você e, portanto, só pode ser "executado" pelo sistema de tempo de execução Haskell, ainda é um Functor
além de ser um Monad
. Na verdade, há um sentido importante em que a Monad
é apenas um tipo especial de Functor
com alguns recursos extras, mas este não é o lugar para entrar nisso.
De qualquer forma, as Mônadas gostam de IO
tipos de mapas para novos tipos que representam "cálculos que resultam em valores" e você pode transformar funções em Monad
tipos por meio de uma fmap
função muito parecida com a chamada liftM
que transforma uma função regular em um "cálculo que resulta no valor obtido pela avaliação do função."
Você provavelmente adivinhou (se já leu até aqui) que Maybe
também é a Monad
. Ele representa "cálculos que podem não retornar um valor". Assim como no fmap
exemplo, isso permite que você faça vários cálculos sem ter que verificar explicitamente os erros após cada etapa. E, de fato, da maneira como a Monad
instância é construída, um cálculo de Maybe
valores para assim que um Nothing
é encontrado, então é como um aborto imediato ou um retorno sem valor no meio de um cálculo.
Você poderia ter escrito talvez
Como eu disse antes, não há nada inerente ao Maybe
tipo que está embutido na sintaxe da linguagem ou no sistema de tempo de execução. Se Haskell não o forneceu por padrão, você mesmo poderia fornecer todas as suas funcionalidades! Na verdade, você mesmo poderia escrever novamente, com nomes diferentes, e obter a mesma funcionalidade.
Esperamos que você entenda o Maybe
tipo e seus construtores agora, mas se ainda houver alguma dúvida, me avise!
Maybe
onde outras línguas usariamnull
ounil
(com sórdidosNullPointerException
s espreitando em cada esquina). Agora, outras linguagens também começam a usar essa construção: Scala asOption
, e até mesmo Java 8 terá oOptional
tipo.