Um dos pesadelos de todo desenvolvedor, é uma NullPointerException
. Tratar objetos que podem conter um valor, ou não, é chato, e muito propenso à erros. Quem nunca esqueceu de fazer um check contra valores nulos?
1
|
|
ou ainda
1 2 |
|
Seria bom se houvesse alguma maneira de evitar esse tipo de problema de Exceptions serem lançadas por que o desenvolvedor esqueceu de tratar um valor que possivelmente veio nulo.
Desde que comecei a me aventurar pelo mundo de linguagens funcionais, percebi que lá, eu não me preocupava com isso. E isso se devia ao fato de que eu usava um tipo de dados bem interessante: o tipo Option
.
1 2 3 4 |
|
Como voce pode ver, o tipo Option simplesmente encapsula um valor possivelmente nulo. E existem os tipos concretos, chamados Some
e None
. Abaixo a declaração de ambos.
1 2 3 4 5 6 7 8 |
|
E também temos um objeto Option com o método apply que cria um valor do tipo Option com base no valor passado como parâmetro:
1 2 3 |
|
Assim, podemos criar valores do tipo Option
da seguinte maneira:
1 2 |
|
1 2 |
|
Como voces podem ver, o encapsulamento do valor é feito, mas ainda sim, podemos cair em uma Exception no caso de tentarmos acessar o value
do Option. Quer dizer então que o Option não serve para nada? Não é bem assim.
O poder verdadeiro do Option está em suas higher-order functions map
e flatMap
. Na definição da trait Option
, temos a declaração dos métodos:
1 2 3 4 5 |
|
Qual a útilidade desses métodos? Simples:
- Ter acesso ao valor armazenado no
Option
, se houver um; - Fazer combinações de valores do tipo
Option
Como assim?
1 2 3 4 |
|
Como vimos, com os métodos flatMap
e map
podemos acessar o valor contido dentro do Option
, também é possível transformar o valor em um outro valor do tipo Option
. No exemplo acima, criamos um Some(10)
e depois o transformamos, multiplicando o valor por 2. Assim, obtemos um Some(20)
. Logo em seguida, usamos o método map
para chamarmos o método println
que imprime o valor na tela. E o legal é que, caso em alguma chamada a map
ou flatMap
, apareça um valor null
, o resultado vai ser um None
. E qualquer chamada a uma dessas funções sobre um valor None
, nada irá acontecer, e assim, nenhuma NullReferenceException
será lançada. Legal não é?
Também é possível obter o valor contido dentro do Option
através de Pattern Matching:
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
Nos exemplos acima, em um caso, o valor é um Some(10)
, então ele cai no primeiro caso, e imprime o valor 10. No segundo caso, como é um None
, ele cai no segundo caso, e imprime “Vazio”.
Um exemplo mais “mundo real” seria:
1 2 3 |
|
No exemplo acima, temos uma lista de pessoas pessoas
, e chamamos o método find
para tentarmos achar dentro dessa lista, uma pessoa chamada “João”. Como o João pode não existir dentro da lista, ele retorna um Option[Pessoa]
. Daí, pegamos o salário do possível valor do joao
com a funcão map
. No final, imprimimos o valor do salário. Mas, como o resultado final pode ser um None
, chamamos mais uma função interessante chamada getOrElse
. O que essa função faz é simples.
1 2 3 4 5 |
|
Como podemos ver, ela faz um Pattern Matching sobre o próprio valor, e caso ele seja um Some[T]
, retornamos o próprio valor, caso seja um None
, retornamos o valor default passado como parâmetro.
Então, no exemplo do João acima, caso o João exista na lista, o seu salário será impresso na tela, caso não, será impresso o valor 0.0.
O legal do Option
é que através da definição do seu tipo, e de suas higher-order functions, torna-se difícil uma NullReferenceException
ser lançada. Toda vez que alguma função possa retornar um valor nulo, basta retornar um valor do tipo Option[T]
, ao invés de simplesmente um valor do tipo T
. A partir daí, é só usar as funções map
e flatMap
, que elas se encarregam de tratar os casos onde o valor for nulo, propagando o None
por toda a cadeia de chamadas.