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.A maioria das respostas atuais são explicações altamente técnicas de como
Just
e os amigos funcionam; Pensei em tentar explicar para que serve.Muitas linguagens têm um valor como
null
esse que pode ser usado em vez de um valor real, pelo menos para alguns tipos. Isso deixou muitas pessoas muito zangadas e foi amplamente considerado uma má jogada. Ainda assim, às vezes é útil ter um valor comonull
indicar a ausência de algo.Haskell resolve esse problema fazendo com que você marque explicitamente os locais onde pode ter um
Nothing
(sua versão de anull
). Basicamente, se sua função normalmente retornaria o tipoFoo
, ela deveria retornar o tipoMaybe Foo
. Se quiser indicar que não há valor, volteNothing
. Se quiser retornar um valorbar
, você deve retornarJust bar
.Então, basicamente, se você não pode ter
Nothing
, você não precisaJust
. Se você pode terNothing
, você precisaJust
.Não há nada de mágico nisso
Maybe
; é construído no sistema de tipo Haskell. Isso significa que você pode usar todos os truques usuais de correspondência de padrões Haskell com ele.fonte
Dado um tipo
t
, um valor deJust t
é um valor existente do tipot
, ondeNothing
representa uma falha em alcançar um valor ou um caso em que ter um valor não faria sentido.No seu exemplo, ter um saldo negativo não faz sentido, então, se isso acontecer, ele é substituído por
Nothing
.Para outro exemplo, isso poderia ser usado na divisão, definindo uma função de divisão que recebe
a
eb
e retornaJust a/b
seb
for diferente de zero eNothing
caso contrário. Muitas vezes é usado assim, como uma alternativa conveniente para exceções, ou como seu exemplo anterior, para substituir valores que não fazem sentido.fonte
Just
, seu código não seria verificado. A razão para issoJust
é manter os tipos adequados. Existe um tipo (uma mônada, na verdade, mas é mais fácil pensar que é apenas um tipo)Maybe t
, que consiste em elementos da formaJust t
eNothing
. Uma vez queNothing
tem tipoMaybe t
, uma expressão que pode ser avaliada como umNothing
ou algum valor de tipot
não foi digitada corretamente. Se uma função retornarNothing
em alguns casos, qualquer expressão que use essa função deve ter alguma maneira de verificar isso (isJust
ou uma instrução case), de modo a tratar todos os casos possíveis.Maybe t
é apenas um tipo. O fato de haver umaMonad
instância paraMaybe
não o transforma em algo que não seja um tipo.Uma função total a-> b pode encontrar um valor do tipo b para cada valor possível do tipo a.
Em Haskell, nem todas as funções são totais. Neste caso particular, a função
lend
não é total - não é definida para o caso em que o saldo é menor que a reserva (embora, a meu gosto, faria mais sentido não permitir que newBalance seja menor que a reserva - como está, você pode pegar 101 emprestado de um saldo de 100).Outros projetos que lidam com funções não totais:
lend
poderia ser escrito para retornar o saldo antigo, se a condição para o empréstimo não for atendidaEssas são limitações de projeto necessárias em linguagens que não podem impor a totalidade das funções (por exemplo, Agda pode, mas isso leva a outras complicações, como tornar-se incompleto).
O problema em retornar um valor especial ou lançar exceções é que é fácil para o chamador omitir o tratamento de tal possibilidade por engano.
O problema de descartar silenciosamente uma falha também é óbvio - você está limitando o que o chamador pode fazer com a função. Por exemplo, se o
lend
saldo antigo for retornado, o chamador não terá como saber se o saldo mudou. Isso pode ou não ser um problema, dependendo da finalidade pretendida.A solução de Haskell força o chamador de uma função parcial a lidar com o tipo como
Maybe a
, ouEither error a
por causa do tipo de retorno da função.Desta forma,
lend
como é definida, é uma função que nem sempre calcula novo saldo - em algumas circunstâncias não é definido novo saldo. Sinalizamos essa circunstância ao chamador retornando o valor especial Nothing ou envolvendo o novo saldo em Just. O chamador agora tem liberdade de escolha: lidar com a falha de empréstimo de uma maneira especial ou ignorar e usar o saldo antigo - por exemplomaybe oldBalance id $ lend amount oldBalance
,.fonte
A função
if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)
deve ter o mesmo tipo deifTrue
eifFalse
.Então, quando escrevemos
then Nothing
, devemos usar oMaybe a
tipoelse f
fonte
Nothing
eJust newBalance
explicitamente.Just
.