Você está na página 1de 46

6Lidando com dados opcionais

Nissocapítulo

A null referência ou “o erro de um bilhão de dólares”


Alternativas para null referências
Desenvolvendo um Option tipo de dados para dados opcionais
Aplicando funções a valores opcionais
Compondo valores opcionais

A representação de dados opcionais sempre foi um problema em programas de


computador. O conceito de dados opcionais é simples. Na vida cotidiana, é fácil
representar a ausência de algo quando esse algo está armazenado em um recipi-
ente – seja ele qual for, pode ser representado por um recipiente vazio. Por exem-
plo, uma ausência de maçãs pode ser representada por uma cesta de maçãs vazia.
A ausência de gasolina em um carro pode ser visualizada como um tanque de ga-
solina vazio. Mas representar a ausência de dados em programas de computador
é um pouco mais difícil.

A maioria dos dados é representada por uma referência que aponta para eles,
portanto, a maneira mais óbvia de representar a ausência de dados é usar um
ponteiro para nada. Isso é o que chamamos deponteiro nulo . Em Kotlin, uma refe-
rência é um ponteiro para um valor.
Na maioria das linguagens de programação, as referências podem ser alteradas
para apontar para um novo valor. Por esse motivo, o termo variável é frequente-
mente usado como substituto de referência . A variável de palavra não é um
grande problema quando todas as referências podem ser reatribuídas posterior-
mente a um novo valor. Nesses casos, é normal usar o termo variável como substi-
tuto de referência . Mas isso pode ser diferente quando você usa referências que
não podem ser reatribuídas. Nesse caso, você acaba com dois tipos de variáveis:
as que podem variar e as que não podem. É então mais seguro usar o termo refe-
rência .

Algumas referências podem ser criadas null e depois alteradas para apontar
para valores. Eles podem até ser alterados novamente para apontar null se os
dados forem removidos. Nesses casos, é seguro chamá-los de variables , ou seja ,
referências de variáveis ​.

Por outro lado, algumas referências não podem ser criadas sem que apontem
para um valor e, uma vez feito isso, não podem ser feitas para apontar para um
valor diferente. Tais referências às vezes são chamadasconstantes . Em Java, por
exemplo, as referências geralmente são variáveis, a menos que sejam declaradas
final , o que as torna constantes. Em Kotlin, as referências variáveis ​são declara-
das com a palavra-chave var , e as não variáveis, também chamadasreferências
imutáveis ​, são declaradas como val .

A princípio, você pode pensar que os dados opcionais só podem ser referenciados
com var . Qual seria o sentido de criar uma referência a uma ausência de dados
se não para eventualmente mudar isso para uma referência a alguns dados? Mas
como você representaria a ausência de dados? A maneira mais comum é usar o
que chamamos dereferência nula . Uma referência nula é uma referência que não
aponta para nada, até que alguns dados sejam atribuídos a ela.

Neste capítulo, você aprenderá como lidar com dados opcionais — valores ausen-
tes que não são resultado de um erro. Começo discutindo problemas com o pon-
teiro nulo. Depois disso, discuto como Kotlin lida com referências nulas e as alter-
nativas de Kotlin para referências nulas. No restante do capítulo, descrevo como
criar e usar o Option tipo como uma solução para trabalhar com dados opcio-
nais. Você pode usar o Option tipo para compor funções mesmo quando os dados
estiverem ausentes.

6.1 Problemas com o ponteiro nulo

Umdos bugs mais frequentes em programas que permitem referências nulas é


o NullPointerException . Essa exceção é gerada quando um identificador é
desreferenciado e não aponta para nada: alguns dados são esperados, mas estão
faltando. Diz-se que tal identificador aponta para null . Nesta seção, você apren-
derá sobre os problemas com o uso de referências nulas.

Em 1965, ao projetar o ALGOL (umlinguagem orientada a objetos), Tony Hoare in-


ventou a referência nula. Mais tarde, ele se arrependeu. Aqui está o que ele disse
sobre isso em 2009: 1 
Eu chamo isso de meu erro de um bilhão de dólares.... Meu objetivo era ga-
rantir que todo uso de referências fosse absolutamente seguro, com verifica-
ção realizada automaticamente pelo compilador. Mas não resisti à tentação
de colocar uma referência nula porque era muito fácil de implementar. Isso
levou a inúmeros erros, vulnerabilidades e falhas do sistema, que provavel-
mente causaram um bilhão de dólares em dor e danos nos últimos quarenta
anos.

Embora agora deva ser de conhecimento comum evitar a referência nula, isso
está longe de ser o caso. A biblioteca padrão Java, que também é usada em progra-
mas Kotlin, contém métodos e construtores que usam parâmetros opcionais que
devem ser definidos null se os parâmetros estiverem ausentes. Veja, por exem-
plo, a java.net.Socket classe. Essa classe define o seguinte construtor:

public Socket(String address,


porta int,
InetAddress localAddr,
int localPort) lança IOException

De acordo com a documentação, 2 

“Se o endereço local especificado for null , é o equivalente a especificar o en-


dereço como o endereço local.”

Aqui, a referência nula é um parâmetro válido. Isso às vezes é chamado denegócio


nulo . Essa forma de lidar com a ausência de dados não é específica de objetos. A
porta também pode estar ausente; mas em Java não pode ser null porque é
primitivo:
Um número de porta local igual a zero permitirá que o sistema selecione uma
porta livre na operação de vinculação.

Esse tipo de valor às vezes é chamado devalor sentinela . Não é usado para o valor
em si (não significa porta 0), mas é usado para especificar a ausência de uma indi-
cação de porta. Você pode encontrar muitos outros exemplos de tratamento da au-
sência de dados na biblioteca padrão Java.

O uso de null para representar a ausência de dados é perigoso porque o fato de


o endereço local null ser não intencional e devido a um erro anterior. Mas esse
uso não causará uma exceção. O programa continuará funcionando, embora não
como pretendido.

Existem outros casos de negócios nulos. Se você tentar recuperar um valor de


um HashMap usando uma chave que não está no mapa, você obterá um arquivo
null . Isso é um erro? Você não sabe. Pode ser que a chave seja válida, mas não
foi registrada no mapa, ou pode ser que a chave seja supostamente válida e deve-
ria estar no mapa, mas houve um erro anterior ao calcular a chave. Por exemplo,
a chave pode ser null , intencionalmente ou devido a um erro, e isso não gerará
uma exceção. Pode até retornar um valor não nulo porque a null chave é permi-
tida em um HashMap . Esta situação é uma bagunça completa. Como programador
profissional, você sabe o que fazer sobre isso:

Você sabe que nunca deve usar uma referência sem verificar se é null ou
não. (Você faz isso para cada parâmetro de objeto recebido por uma função,
certo?)
Você sabe que nunca deve obter um valor de um mapa sem primeiro testar se
o mapa contém a chave correspondente.
Você sabe que nunca deve tentar obter um elemento de uma lista sem verificar
primeiro se a lista não está vazia e se possui elementos suficientes se você esti-
ver acessando o elemento por meio de seu índice.

Se você fizer qualquer um desses, você nunca terá um NullPointerException


ou um IndexOutOfBoundsException , e você pode conviver com referências nu-
las sem nenhuma proteção específica. Mas se você esquecer e às vezes receber es-
ses tipos de exceções desagradáveis, talvez queira uma maneira mais fácil e se-
gura de lidar com a ausência de um valor, seja intencional ou resultante de um
erro. Neste capítulo, você aprenderá como lidar com valores ausentes que não são
resultado de um erro. Esse tipo de dado é chamadodados opcionais .

Sempre há truques para lidar com dados opcionais. Uma das mais conhecidas e
mais utilizadas é a lista. Quando uma função deve retornar um valor ou nada, al-
guns programadores usam a lista como valor de retorno. A lista pode conter 0 ou
1 elemento. Embora isso funcione muito bem, tem váriosdesvantagens:

Não há como garantir que a lista contenha no máximo um elemento. O que


você deve fazer se receber uma lista de vários elementos?
Como você pode distinguir entre uma lista que deve conter no máximo um ele-
mento e uma lista regular?
A List classe define muitas funções para lidar com o fato de que as listas po-
dem conter vários elementos. Essas funções são inúteis para o seu caso de uso.
As listas são estruturas complexas e você não precisa disso. Uma implementa-
ção muito mais simples é suficiente.
6.2 Como o Kotlin lida com referências nulas

DentroNo capítulo 2, você aprendeu como o Kotlin lida com referências nulas. Ti-
pos normais em Kotlin não podem ser nulos. Se uma referência recebe esse tipo
de tipo, ela nunca pode ser nula, seja a val (constante) ou a var (variável). Um
nulo val faria pouco sentido. Um null var , por outro lado, pode parecer útil. Às
vezes, você pode querer declarar uma referência de um determinado tipo sem ter
nenhum valor atribuído a ela. Mas você ainda deve declarar a referência em al-
gum ponto para colocá-la no escopo correto. Considere o seguinte exemplo:

while(enumeração.hasNext()) {
resultado = processo(resultado, enumeração.próximo())
}
usar (resultado)

Neste exemplo, os elementos de uma enumeração são combinados dentro de um


loop que é executado até que não haja mais elementos. Onde você declararia re-
sult e qual valor inicial você daria? Uma solução que vem à mente é processar o
primeiro valor de enumeração fora do loop:

var resultado = processo(enumeração.próximo())


while(enumeração.hasNext()) {
resultado = processo(resultado, enumeração.próximo())
}
usar (resultado)

Mas isso causaria um erro se a enumeração não tivesse nenhum elemento. A solu-
ção comum é declarar a result referência como null :
var resultado = nulo
while(enumeração.hasNext()) {
resultado = processo(resultado, enumeração.próximo())
}
usar (resultado)

Agora, a process funçãodeve lidar com o caso quando result é null . (No pri-
meiro caso, ele foi sobrecarregado com uma versão que aceita um único argu-
mento.) Um null resultado agora é um negócio nulo, o que significa que é a pri-
meira iteração. Mas Kotlin não permitirá que você faça isso.

Kotlin usa referências nulas, mas força você a informar ao compilador que sabe
que a referência pode ser nula. Isso não vai impedir NullPointException , mas
obriga você a aceitar a responsabilidade de fazê-lo. É como um aviso: se você usar
a null referência, estará por sua conta. O compilador do Kotlin pode ajudá-lo de
várias maneiras, o que é ótimo para programadores que usam referências mutá-
veis. Os programadores que evitam mutações, por outro lado, não usam referên-
cias nulas. Mas a maneira como o Kotlin representa os tipos anuláveis ​ainda é
uma grande ajuda para uma programação segura. Com o Kotlin, os programado-
res podem garantir que nunca haverá referências nulas em seu código. (Consulte
o capítulo 2 para obter uma breve descrição das diferenças entre tipos anuláveis ​e
não anuláveis ​em Kotlin.)

Como você deve se lembrar do capítulo 2, as referências de tipos regulares não


podem ser definidas como null . Se você quiser definir referências para null ,
precisará usar tipos anuláveis. Os tipos anuláveis ​são escritos da mesma forma
que os tipos regulares não anuláveis, mas com um ponto de interrogação (?). Uma
Int referência nunca pode ser definida como, null mas uma Int? referência
pode. A partir de agora, você usará apenas tipos não anuláveis, portanto, nunca
encontrará um NullPointException em seucódigo.

6.3 Alternativas para referências nulas

Agora,você sabe que nunca haverá nenhum NullPointerException resultado


do seu código, não é? Bem, não é tão simples assim porque muitas funções nas bi-
bliotecas Kotlin aceitam parâmetros anuláveis ​e retornam valores de tipos anulá-
veis. As funções que recebem argumentos anuláveis ​não são um problema, pois
os tipos não anuláveis ​são subtipos dos anuláveis ​correspondentes. Int é um sub-
tipo de Int? , String é um subtipo de String? , e assim por diante, então você
sempre pode chamar uma função que aceita um tipo anulável com um argu-
mento do tipo não anulável correspondente.

No entanto, os valores de retorno são muito mais problemáticos. Veja o exemplo


de um Map . O código a seguir não será compilado em Kotlin:

val mapa: Mapa<String, Pessoa> = ...


val pessoa: Pessoa = mapa["Joe"]

A razão é que map["Joe"] é açúcar sintático sobre map.get("Joe") , e a


get função de Map deve retornar um Person , se um foi encontrado para a
chave "Joe" , ou nada. Como uma função pode não retornar nada? Ao retornar
null . Mesmo que você se esforce para banir referências nulas de seu próprio có-
digo, sempre acabará obtendo algumas das bibliotecas que usa. Você não pode, no
entanto, usar bibliotecas, exceto aquelas de sua propriedade. Esta é uma solução,
mas envolve muito trabalho. Outra solução é envolver chamadas de função que
retornam null em alguma função de wrapper retornando outra coisa. Mas o
que?

O uso de bibliotecas não é a única causa possível do problema. O código a seguir é


um exemplo de uma função que retorna dados opcionais:

fun mean(list: List<Int>): Double = quando {


list.isEmpty() -> TODO("O que você deve retornar?")
else -> list.sum().toDouble() / list.size
}

a mean função é um exemplo de função parcial, como você viu no capítulo 3. Ela é
definida para todas as listas de números, exceto para a lista vazia. Como você
deve lidar com o caso de lista vazia? Uma possibilidade é retornar um valor senti-
nela. Que valor você poderia escolher? Como o tipo é Double , você pode usar um
valor definido na Double classe:

fun mean(list: List<Int>): Double = quando {


list.isEmpty() -> Double.NaN
else -> list.sum().toDouble() / list.size
}

Isso funcionaporque Double.NaN (não um número) é um Double valor. Até aí


tudo bem, mas você ainda tem três problemas:

E se você precisar aplicar o mesmo princípio a uma função que retorna um


Int ? Não há equivalente ao NaN valor na Int classe.
Como você pode sinalizar ao usuário de sua função que ela pode retornar um
valor sentinela?
Como você pode lidar com uma função paramétrica, como a seguinte?

fun <T, U> f(lista: Lista<T>): U = quando {


list.isEmpty() -> ???
else -> ... (alguma computação produzindo um U)
}

Outra solução é lançar uma exceção:

fun mean(list: List<Int>): Double = quando {


list.isEmpty() -> throw IllegalArgumentException("Lista vazia")
else -> list.sum().toDouble() / list.size
}

Mas esta solução é feia e cria mais problemas do que resolve:

As exceções geralmente são usadas para resultados incorretos, mas aqui não
há erro. Não há resultado e isso ocorre porque não há dados de entrada! Ou
você deve considerar chamar a função com uma lista vazia de bug?
Que exceção você deve lançar? Um personalizado? Ou um padrão?
A função não é mais uma função pura e não pode mais ser composta com ou-
tras funções. Para compô-lo, você precisa recorrer a estruturas de controle
como try..catch , que nada mais são do que uma forma moderna de goto .

Você também pode retornar null e deixar o chamador lidar com isso:
fun mean(list: List<Int>): Duplo? = quando {
list.isEmpty() -> nulo
else -> list.sum().toDouble() / list.size
}

Em linguagens comuns, retornar null é a pior solução possível. Veja a con-


sequência em uma linguagem como Java:

Ele força (idealmente) o chamador a testar o resultado null e agir de acordo.


Ele vai bater com um NullPointerException se o boxing for usado porque
uma null referência não pode ser desempacotada em um valor primitivo.
Como na solução de exceção, a função não pode mais ser composta.
Permite que o problema potencial se propague para longe de sua origem. Se o
chamador esquecer de testar um null resultado, um NullPointerExcepti-
on pode ser lançado de qualquer lugar no código.

Kotlin torna as coisas um pouco melhores:

Ele força o chamador a lidar com o null caso escolhendo um tipo de retorno
anulável.
Ele não travará no boxe porque o Kotlin não possui tipos primitivos, portanto,
não há boxe (pelo menos no nível do usuário).
Oferece operadores que permitem compor funções retornando um tipo anulá-
vel de forma segura. (Consulte o capítulo 2 para obter detalhes.)
Embora não impeça a propagação do problema, torna o problema menos cru-
cial ao propagar a obrigação de tratá-lo.
Como eu disse anteriormente, a solução Kotlin é melhor do que a ausência de
uma solução com a qual você tenha que lidar em outras linguagens, mas ainda
está longe do ideal. Uma solução melhor seria pedir ao usuário para fornecer um
valor especial que será retornado se nenhum dado estiver disponível. Por exem-
plo, a função a seguir calcula o valor máximo de uma lista. Ele usa a
List.max() função do Kotlin, retornando Int? , mas trata o caso de uma lista
vazia para que você possa fazer sua função retornar Int ao invés de Int? :

fun max(lista: List<Int>, padrão: Int): Int = quando {


list.isEmpty() -> padrão
senão -> lista.max()
}

Mas isso não vai compilar porque list.max() o tipo de retorno é Int? . (Nunca
será, null mas Kotlin não vê isso.) Você sabe que a solução idiomática Kotlin é

fun max(lista: List<Int>, padrão: Int): Int = list.max() ?: padrão

Mas e se você não tiver um valor padrão? Às vezes, um valor padrão será forne-
cido posteriormente. Ou você deseja aplicar um efeito ao resultado se houver um
valor e não fazer nada caso contrário. Veja o exemplo a seguir usando a max fun-
ção anterior:

val max = max(listaOf(1, 2, 3), 0)

println("O máximo é $max")


Se a lista estiver vazia, isso imprimirá The Maximum is 0 , que pode não ser a
saída correta. Afinal, o máximo não é 0; não há máximo. você poderia escrever

val max = max(listaOf(1, 2, 3), 0)

print(if (max != 0) "O máximo é $max\n" else "")

Mas isso seria incorreto se a lista contivesse apenas 0(s). Você pode usar a forma
tradicional:

val max = listOf(1, 2, 3).max()

if (max != null) println("O máximo é $max")

Mas agora você tem que lidar com referências nulas novamente. Tem que haver
um melhorsolução!

6.4 Usando o tipo de opção

DentroNo restante deste capítulo, você criará o Option tipo para lidar com dados
opcionais. O Option tipo que você compõe será semelhante ao List tipo. Você a
implementará como uma classe selada abstrata Option , contendo duas subclas-
ses internas que representam a presença e a ausência de dados.

Chamaremos a subclasse que representa a ausência de dados None e a subclasse


que representa a presença de dados Some . A Some conterá o valor de dados cor-
respondente. A Figura 6.1 mostra como o Option tipo pode alterar a forma como
as funções podem ser compostas, e a próxima listagemmostra o código para o
Option tipo de dados.

Figura 6.1 Usando um Option tipo para dados opcionais permite compor funções mesmo quando os dados es-
tão ausentes. Sem o Option tipo, as funções de composição não produziriam uma função porque o programa
resultante lançaria potencialmente um arquivo NullPointerException .

Listagem 6.1 O Option tipo de dados

classe selada Option<out A> { ①

diversão abstrata isEmpty(): Booleano

objeto interno Nenhum: Option<Nada>() { ②

sobrescrever diversão isEmpty() = true

override fun toString(): String = "None"

substituir diversão é igual a (outro: Qualquer?): Booleano =


outro === Nenhum ③
substituir fun hashCode(): Int = 0
}

classe de dados interna Some<out A>(valor de val interna: A): Option<A>() { ④

sobrescrever diversão isEmpty() = false


}

objeto complementar {

operador divertido <A> invocar(a: A? = null): Opção<A> ⑤


= quando (a) {
nulo -> Nenhum
else -> Some(a)
}
}
}

① A opção torna-se covariante em A.

② None é um objeto singleton que será usado para todos os tipos.

③ Todas as ausências de dados são consideradas iguais.

④ Instâncias de Some são consideradas iguais se seus valores forem iguais, que é o
que você obtém usando uma classe de dados.

⑤ A função de chamada é declarada com a palavra-chave operator.


Nesta listagem, você pode ver o quão próximo Option está de List . Ambos são
classes abstratas com duas implementações internas. A None subclasse corres-
ponde a Nil e a Some subclasse a Cons . Observe também que, com a invo-
ke função, usar null para o valor padrão pode ser visto como um truque sujo.
Sinta-se à vontade para sobrecarregar a função com uma versão sem argumentos,
se preferir. Agora você pode usar Option para sua definição da max função con-
forme mostrado aqui:

fun max(list: List<Int>): Option<Int> = Option(list.max())

Do jeito que está, a Option classe não é útil. Tudo o que você pode fazer é testar
se o resultado está vazio ou imprimi-lo. Você poderia ter adicionado uma função
para retornar o valor, mas esta função teria que lançar uma exceção ou retornar
null quando chamada em None . Retornar null iria anular todo o propósito de
Option . Lançar exceções é ruim porque não compõe ou, pelo menos, não com-
põe da mesma forma que outros resultados compõem. Se você conhece Java, sabe
que o Optional class em Java tem um get método retornando o valor se pre-
sente ou lançando uma exceção caso contrário.

Aqui está o que Brian Goetz, Java Language Architect da Oracle, diz sobre isso: 3 
Anúncio de serviço público: NUNCA ligue para Optional.get a menos que você
possa provar que nunca será nulo; em vez disso, use um dos métodos seguros
como orElse ou ifPresent. Em retrospecto, deveríamos ter chamado get algo
comogetOrElseThrowNoSuchElementException ou algo que deixou muito
mais claro que este era um método altamente perigoso que prejudicou todo o
propósito de Optional em primeiro lugar. Lição aprendida.

A realidade é muito mais simples: não deveria haver nenhum get método em
Java Optional e você também não colocará um na Option classe. Aqui está o
motivo: Option é um contexto computacional para lidar com dados opcionais
com segurança. Dados opcionais são potencialmente inseguros, e é por isso que
você precisa de um contexto seguro para colocá-los. Como consequência, você
nunca deve retirar um valor desse contexto sem primeiro torná-lo seguro.

6.4.1Obtendo um valor de uma opção

Muitosfunções que você criou List também serão úteis para arquivos Option .
Mas antes de criar essas funções, vamos começar com alguns Option usos especí-
ficos. Lembre-se do uso de um valor padrão?

Exercício 6.1

Implementar uma getOrElse funçãoque retorna o valor contido, se existir, ou


um padrão fornecido. Veja como a assinatura da função pode parecer:

fun getOrElse(padrão: A): A


Solução

Essa função é implementada na Option classe e recebe um parâmetro do tipo A ,


que não é compatível com covariância. Você deve desativar a verificação de varia-
ção usando o @UnsafeVariance anotação conforme explicado no capítulo 5:

fun getOrElse(padrão: @UnsafeVariance A): A = quando (este) {


é Nenhum -> padrão
é algum -> valor
}

Agora você pode definir funções que retornam opções e usam o valor retornado
de forma transparente da seguinte maneira (usando a List classe Kotlin padrão):

val max1 = max(listOf(3, 5, 7, 2, 1)).getOrElse(0)


val max2 = max(listOf()).getOrElse(0)

Aqui, max1 é igual a 7 (o valor máximo na lista) e max2 é definido como 0 (o valor
padrão). Mas você ainda pode ter um problema. Observe o seguinte exemplo:

fun getDefault(): Int = throw RuntimeException()

fun main(args: Array<String>) {


val max1 = max(listOf(3, 5, 7, 2, 1)).getOrElse(getDefault())
println(max1)
val max2 = max(listOf()).getOrElse(getDefault())
println(max2)
}
Este exemplo é um pouco artificial. o getDefault função não é uma função. Mas
isso é apenas para mostrar o que está acontecendo. O que este exemplo impri-
mirá? Se você acha que imprimirá 7 e lançará uma exceção, dê outra olhada no
código.

Este exemplo não imprime nada e lançará uma exceção diretamente porque Ko-
tlin é uma linguagem estrita. Os parâmetros da função são avaliados antes que a
função seja executada, sejam eles necessários ou não. Isso significa que o getO-
rElse parâmetro da função é avaliado em qualquer caso, seja chamado em a So-
me ou a None . O fato de o parâmetro da função não ser necessário para a Some é
irrelevante. Isso não faz diferença quando o parâmetro é literal, mas faz uma
grande diferença quando é uma chamada de função. A getDefault função será
chamada em qualquer caso, então a primeira linha lançará uma exceção e nada
será exibido. Isso geralmente não é o que você deseja.

Exercício 6.2

Corrija o problema anterior, adicionando uma nova versão do getOrElse função


com um parâmetro avaliado lentamente.

Dica

Em vez de usar um valor literal, use uma função sem argumento que retorne o
valor.

Solução

Aqui está a implementação para esta função na Option classe pai:


fun getOrElse(padrão: () -> @UnsafeVariance A): A = quando (este) {
é Nenhum -> default()
é algum -> valor
}

Na ausência de um valor, o parâmetro é avaliado por meio de uma chamada à


função fornecida. O max exemplo agora pode ser reescrito da seguinte forma:

val max1 = max(listOf(3, 5, 7, 2, 1)).getOrElse(::getDefault)


println(max1)
val max2 = max(listOf()).getOrElse(::getDefault)
println(max2)

Este programa imprime 7 no console antes de lançar umexceção.

6.4.2 Aplicando funções a valores opcionais

Umfunção importante em List é a map função, que permite aplicar uma função
de A a B para cada elemento de uma lista de A , produzindo uma lista de B . Con-
siderando que an Option é como uma lista contendo no máximo um elemento,
você pode aplicar o mesmo princípio.

Exercício 6.3

Criar uma map funçãopara transformar um Option<A> em um Option<B> apli-


cando uma função de A para B .
Dica

Você pode definir uma função abstrata na Option classe com uma implementa-
ção em cada subclasse. Alternativamente, você pode implementar a função
no Option classe. No primeiro caso, esteja ciente do tipo na None classe. A assina-
tura da função abstrata em Option será esta:

diversão abstrata <B> mapa(f: (A) -> B): Opção<B>

Solução

A None implementação é simples. Você precisa retornar o None singleton. Ob-


serve que a função a ser mapeada deve ser do tipo (Nothing) -> B :

override fun <B> map(f: (Nothing) -> B): Option<B> = None

A Some implementação não é muito mais complexa. Tudo o que você precisa fa-
zer é obter o valor, aplicar a função a ele e agrupar o resultado em um novo
Some :

override fun <B> map(f: (A) -> B): Option<B> = Some(f(value))

Como alternativa, você pode preferir implementar a função no Option paiclasse:

fun <B> map(f: (A) -> B): Opção<B> = quando (este) {


é Nenhum -> Nenhum
é Some -> Some(f(valor))
}

6.4.3 Lidando com a composição da Opção

Comovocê logo perceberá que as funções de A a B não são as mais comuns em


programas seguros. A princípio, você pode ter problemas para se familiarizar
com as funções que retornam valores opcionais. Afinal, parece envolver trabalho
extra para agrupar valores em Some instâncias. Mas com mais prática, você verá
que essas operações ocorrem apenas raramente.

Ao encadear funções para criar uma computação complexa, você frequentemente


começará com um valor retornado por alguma computação anterior e passará o
resultado para uma nova função sem ver o resultado intermediário. Você usará
com mais frequência funções de A a do Option<B> que funções de A a B . Pense
na List turma. Será que isso soa um sino? Sim, leva ao flatMap função.

Exercício 6.4

Crie uma flatMap função de instância tomando como argumento uma função de
A to Option<B> e retornando um Option<B> .

Dica

Você pode definir implementações diferentes em ambas as subclasses, mas deve


tentar criar uma implementação única que funcione para ambas as subclasses e
colocá-la na Option classe. Sua assinatura será
fun <B> flatMap(f: (A) -> Opção<B>): Opção<B>

Tente usar algumas das funções que você já definiu ( map e getOrElse ).

Solução

A solução trivial seria definir uma função abstrata na Option classe, retor-
nando None na None classe e retornando Some(f(value)) na Some classe. Esta
é provavelmente a implementação mais eficiente. Mas uma solução mais elegante
é mapear a f função, dando um Option<Option<B>>, e então usar o getOrEl-
se função para extrair o valor ( Option<B> ), fornecendo None como valor
padrão:

fun <B> flatMap(f: (A) -> Option<B>): Option<B> = map(f).getOrElse(None)

Exercício 6.5

Assim como você precisava de uma maneira de mapear uma função que retor-
nasse um Option (conduzindo a flatMap ), você precisaria de uma versão de va-
lores padrão de getOrElse for s. Option Crie o orElse função com a seguinte
assinatura:

fun orElse(padrão: () -> Opção<A>): Opção<A>


Dica

Como você pode imaginar pelo nome, não há necessidade de obter o valor para
implementar essa função. É assim que Option é usado principalmente - por meio
da Option composição, em vez de agrupar e obter valores. Uma consequência é
que a mesma implementação funciona para ambas as subclasses. Não se esqueça,
no entanto, de cuidar da variância.

Solução

A solução consiste em mapear a função _ -> this , que resulta em um


Option<Option<A>> , e então usar getOrElse este resultado com o valor pa-
drão fornecido. O _ na função é usado por convenção para argumentos que não
são usados. Representa o valor contido em Option . O valor ainda é usado, por-
que está contido em this (se estiver presente). Para permitir a variação, você
pode usar a @UnsafeVariance anotação:

fun orElse(padrão: () -> Opção<@UnsafeVariance A>): Opção<A> =


mapa { _ -> this }.getOrElse(padrão)

Você também pode usar uma sintaxe simplificada para a função:

fun orElse(padrão: () -> Opção<@UnsafeVariance A>): Opção<A> =


mapa { this }.getOrElse(padrão)

A { x } sintaxe é a maneira mais simples de escrever qualquer função constante


retornando x , seja qual for o argumento usado.
Exercício 6.6

No capítulo 5, você criou uma filter função para remover de uma lista todos os
elementos que não satisfizeram uma condição expressa na forma de um predi-
cado (ou seja, uma função que retorna um Boolean ). Crie a mesma função para
Option . Aqui está sua assinatura:

fun filter(p: (A) -> Boolean): Opção<A>

Dica

Como an Option é como a List com no máximo um elemento, a implementação


parece trivial. Na None subclasse, você pode retornar None (ou this ). na So-
me classe, você pode devolver o original this se a condição for válida e No-
ne caso contrário. Mas tente criar uma implementação mais inteligente que se en-
caixe na Option classe pai.

Solução

A solução é para flatMap a função usada no Some caso:

fun filter(p: (A) -> Booleano): Opção<A> =


flatMap { x -> if (p(x)) this else Nenhum }

Observe que é prática comum usar o nome p (para predicado ), que é uma função
que retorna um Boolean .
6.4.4 Casos de uso de opções

Sevocê já conhece a Optional classe Java, você deve ter notado que Optio-
nal contém um isPresent() método que permite testar se o Optional contém
um valor ou não. ( Optional tem uma implementação diferente que não é base-
ada emduas subclasses diferentes.) Você poderia implementar facilmente essa
função em sua Option classe Kotlin, embora a chamasse isSome() porque testa-
ria se o objeto é a Some ou a None . Você também pode chamá-lo isNone() de , o
que pode parecer mais lógico, porque seria o equivalente à
List.isEmpty() função.

Embora a isSome() função às vezes possa ser útil, não é a melhor maneira de
usar a Option classe. Se você fosse testar um Option através de
um isSome() função antes de chamar algum tipo de getOrThrow() para obter o
valor, não seria muito diferente de testar uma referência null antes de desrefe-
renciá-la. A única diferença seria se você esquecesse de testar primeiro: você cor-
reria o risco de ver um IllegalStateException ou NoSuchElementExcepti-
on ouqualquer exceção que você escolheu para a None implementação de ge-
tOrThrow() , em vez de um NullPointerException .

A melhor forma de usar Option é através da composição. Para fazer isso, você
deve criar todas as funções necessárias para todos os casos de uso. Esses casos de
uso correspondem ao que você faria com o valor após testar se não é null . Você
poderia, por exemplo

Use o valor como entrada para outra função


Aplicar um efeito ao valor
Use o valor se não for nulo ou use um valor padrão para aplicar uma função
ou um efeito

O primeiro e o terceiro casos de uso já foram possíveis por meio das funções que
você já criou. A aplicação de um efeito pode ser feita de diferentes maneiras, so-
bre as quais você aprenderá no capítulo 12.

Por exemplo, veja como você pode usar a Option classe para alterar a maneira
como usaria um mapa. Na listagem 6.2 , você usará uma função de extensão on
Map para retornar um Option ao consultar uma determinada chave. A imple-
mentação Kotlin padrão de Map.get(key) , que você pode usar como
Map[key] , retorna null se a chave não for encontrada. Você vai usar o getOp-
tion função de extensão para retornar um arquivo Option .

Listagem 6.2 Usando Option para retornar um valor de um Map

import com.fpinkotlin.optionaldata.exercise06.Option
import com.fpinkotlin.optionaldata.exercise06.getOrElse

classe de dados Toon (val firstName: String,


val últimoNome: String,
e-mail val: Option<String> = Option()) {

objeto complementar {
operador divertido invocar(primeiroNome: String,
últimoNome: String,
e-mail: String? = nulo) =
Toon(firstName, lastName, Option(email))
}
}
fun <K, V> Mapa<K, V>.getOption(chave: K) =
Opção(esta[tecla]) ①

fun main(args: Array<String>) {

val toons: Map<String, Toon> = mapOf(


"Mickey" para Toon("Mickey", "Mouse", "mickey@disney.com"),
"Minnie" para Toon("Minnie", "Rato"),
"Donald" para Toon("Donald", "Pato", "donald@disney.com"))

val mickey =
toons.getOption("Mickey").flatMap { it.email } ②
val minnie = toons.getOption("Minnie").flatMap { it.email }
val goofy = toons.getOption("Goofy").flatMap { it.email }

println(mickey.getOrElse { "Sem dados" })


println(minnie.getOrElse { "Sem dados" })
println(goofy.getOrElse { "Sem dados" })
}

① Função de extensão para implementar o padrão de verificação antes de usar para


evitar o retorno de referências nulas

② Composição de opções através do flatMap

Observe que Option permite encapsular na implementação do mapa o padrão


para consultar o mapa com containsKey antes de ligar get .
Neste programa simplificado, você pode ver como várias funções que retornam
Option podem ser compostas. Você não precisa testar nada e não arrisca um
NullPointerException , embora possa estar pedindo o e-mail de um Toon que
não tem, ou mesmo de um Toon que não existe no mapa. Uma solução Kotlin
mais idiomática, usando operadores de tipo anulável, é mostrada na listagem a
seguir.

Listagem 6.3 Usando tipos anuláveis ​em um estilo Kotlin mais idiomático

fun main(args: Array<String>) {

val toons: Map<String, Toon> = mapOf(


"Mickey" para Toon("Mickey", "Mouse", "mickey@disney.com"),
"Minnie" para Toon("Minnie", "Rato"),
"Donald" para Toon("Donald", "Pato", "donald@disney.com"))

val mickey = toons["Mickey"]?.email ?: "Sem dados"


val minnie = toons["Minnie"]?.email ?: "Sem dados"
val goofy = toons["Goofy"]?.email ?: "Sem dados"

println(mickey)
println(minnie)
println(pateta)
}

Nesta fase, você pode achar o estilo Kotlin mais conveniente. Mas há um pequeno
problema. Ambos os programas exibem o seguinte resultado:
Some(value=mickey@disney.com)
Nenhum
sem dados

A primeira linha é o e-mail do Mickey. A segunda linha diz “ None ” porque Min-
nie não tem e-mail. A terceira linha diz “ No data ” porque o Pateta não está no
mapa. Claramente, você precisa de uma maneira de distinguir esses dois casos,
mas nem o uso de tipos anuláveis ​nem a Option classe permitem distinguir entre
os dois. No capítulo 7, você verá como resolver esse problema.

Exercício 6.7

Implemente a variance função de valor em termos de flatMap . A variância de


uma série de valores representa como esses valores são distribuídos em torno da
média. (Isso não está relacionado à variação do tipo.) Se todos os valores estive-
rem próximos da média, a variação é baixa. Uma variância de 0 é obtida quando
todos os valores são iguais à média. A variância de uma série é a média de
Math.pow(x - m, 2) cada elemento x da série, onde m é a média da série. Essa
função deve ser implementada no nível do pacote (fora da Option classe). Aqui
está sua assinatura:

variância val: (List<Double>) -> Option<Double>

Dica

Para implementar esta função, você deve primeiro criar uma mean função. Se
você tiver problemas para definir a mean função, consulte o capítulo 5 ou use o
seguinte:

val significa: (List<Double>) -> Option<Double> = { list ->


quando {
lista.isEmpty() -> Opção()
else -> Option(list.sum() / list.size)
}
}

Solução

Depois de definir a mean função, a variance função é bem simples:

variância val: (List<Double>) -> Option<Double> = { list ->


mean(lista).flatMap { m ->
média(lista.map { x ->
Math.pow((x - m), 2.0)
})
}
}

Usar funções de valor não é obrigatório, mas você deve usar funções de valor se
precisar passá-las como argumentos para HOFs. Quando você só precisa aplicá-
los, no entanto, fun as funçõespode ser mais simples de usar. Se você preferir
usar fun as funçõesquando possível, você pode chegar à seguinte solução:

fun mean(list: List<Double>): Option<Double> =


quando {
lista.isEmpty() -> Opção()
else -> Option(list.sum() / list.size)
}

fun variance(list: List<Double>): Option<Double> =


mean(lista).flatMap { m ->
média(lista.map { x ->
Math.pow((x - m), 2.0)
})
}

Como você pode ver, fun as funçõessão mais simples de usar porque os tipos são
mais simples. Por esse motivo, você usará fun funçõesem vez de funções de valor
sempre que possível. Além disso, é fácil mudar de um para outro. Dada esta
função

fun aToBfunction(a: A): B {


Retorna ...
}

você pode criar uma função de valor equivalente escrevendo

val aToBfunction: (A) -> B = { a -> aToBfunction(a) }

Ou você pode usar uma referência de função:

val aToBfunction: (A) -> B = ::aToBfunction


Por outro lado, você pode criar uma fun função a partir da função de valor
anterior:

fun função aToB(a: A): B = função aToB(a)

Como demonstra a implementação de variance , usando flatMap você pode


construir uma computação com vários estágios, qualquer um dos quais pode fa-
lhar. A computação é interrompida assim que a primeira falha é encontrada por-
que None.flatMap(f) retorna imediatamente None sem tentar aplicar f .

6.4.5 Outras formas de combinar opções

decidindousar Option pode parecer ter enormes consequências. Em particular,


alguns desenvolvedores acreditam que seu código legado ficará obsoleto. O que
você pode fazer agora que precisa de uma função de Option<A> para
Option<B> e só tem uma API com funções para converter um A em um B ? Você
precisa reescrever todas as suas bibliotecas? De jeito nenhum. Você pode facil-
mente adaptá-los.

Exercício 6.8

Definir uma lift funçãoque recebe uma função de A para B como seu argu-
mento e retorna uma função de Option<A> para Option<B> . Como de costume,
use as funções que você já definiu. A Figura 6.2 mostra como a lift função
funciona.
Figura 6.2 Levantando uma função

Dica

Use a map funçãopara criar uma fun função no nível do pacote.

Solução

A solução é bem simples:

fun <A, B> lift(f: (A) -> B): (Opção<A>) -> Opção<B> = { it.map(f) }

A maioria de suas bibliotecas existentes não conterá funções de valor; eles conte-
rão apenas fun funções. Converter uma fun função que recebe um A como argu-
mento e retorna a B em uma função de Option<A> para Option<B> é fácil. Por
exemplo, o levantamento da função String.toUpperCase pode ser feito desta
forma:

val upperOption: (Option<String>) -> Option<String> =


elevador { it.toUpperCase() }

Ou você pode usar uma referência de função:

val upperOption: (Option<String>) -> Option<String> =


elevador(String::toUpperCase)

Exercício 6.9

A lift função anterioré inútil para funções que lançam exceções. Escreva uma
versão lift que funcione com funções que lançam exceções.

Solução

Para este exercício, você precisa agrupar a implementação da função retornada


por lift em um try…catch bloco, retornando None se uma exceção for
lançada:

fun <A, B> lift(f: (A) -> B): (Opção<A>) -> Opção<B> = {
tentar {
it.map(f)
} catch (e: Exceção) {
Opção()
}
}

Você também pode precisar transformar a função de A para B em uma função de


A para Option<B> . Você pode aplicar a mesma técnica:

fun <A, B> hLift(f: (A) -> B): (A) -> Opção<B> = {
tentar {
Opção(it).map(f)
} catch (e: Exceção) {
Opção()
}
}

Observe que essa abordagem não é útil porque a exceção é perdida. No capítulo 7,
você aprenderá como resolver esse problema.

E se você quiser usar uma função herdada com dois argumentos? Digamos que
você queira usar o Integer.parseInt(String s, int radix) método Ja-
vacom um Option<String> e um Option<Integer> . Como você pode fazer
isso? A primeira etapa é criar uma função de valor a partir desse método Java.
Isso é simples:

val parseWithRadix: (Int) -> (String) -> Int =


{ raiz -> { string -> Integer.parseInt(string, raiz) } }

Eu inverti os argumentos aqui e criei uma função curry. Isso faz sentido porque
inverter os argumentos produz (aplicando apenas o primeiro argumento) uma
função que permite analisar todas as strings com um determinado radix . Isso
pode ser útil, embora não inverter os argumentos produza uma função que per-
mite analisar uma determinada string com qualquer radix . Isso geralmente é
muito menos útil, embora dependa do seu caso de uso específico:

val parseHex: (String) -> Int = parseWithRadix(16)

O inverso (aplicando um String primeiro) faria muito menos sentido.

Exercício 6.10

Escreva uma função map2 que receba como argumentos an Option<A> , an


Option<B> , e uma função from (A, B) to C no formato curried e, em seguida,
retorne an Option<C> .

Dica

Use o flatMap e possivelmenteas map funções.

Solução

Aqui está a solução, usando flatMap e map . É importante entender esse padrão
e você o encontrará com frequência. Voltaremos a isso no capítulo 8:

fun <A, B, C> map2(oa: Opção<A>,


ob: Opção<B>,
f: (A) -> (B) -> C): Opção<C> =
oa.flatMap { a -> ob.map { b -> f(a)(b) } }
Com map2 , agora você pode usar qualquer função de dois argumentos como se ti-
vesse sido criada para manipular Option . E as funções com mais argumentos?
Aqui está um exemplo de uma map3 função:

fun <A, B, C, D> map3(oa: Opção<A>,


ob: Opção<B>,
oc: Opção<C>,
f: (A) -> (B) -> (C) -> D): Opção<D> =
oa.flatMap { a ->
ob.flatMap { b ->
oc.map { c ->
f(a)(b)(c)
}
}
}

Você vê o padrão? (O fato de a última função ser map e não flatMap se deve ape-
nas ao fato de f retornar um valor bruto. Se ela retornou um Option , você
ainda pode usar esse padrão substituindo map por flatMap .)

6.4.6 Compondo Lista com Opção

Composição Option instâncias não é tudo que você precisa. Em algum momento,
cada novo tipo definido deve ser composto por qualquer outro. No capítulo ante-
rior, você definiu o List tipo. Para escrever programas úteis, você precisa ser ca-
paz de compor List e Option .

A operação mais comum é converter um List<Option<A>> em um arquivo


Option<List<A>> . A List<Option<A>> é o que você obtém ao mapear a
List<B> com afunção de B para Option<A> . Normalmente, o que você precisa
para o resultado é Some<List<A>> se todos os elementos forem Some<A> , e No-
ne se pelo menos um elemento for a None<A> . Este é apenas um resultado possí-
vel. Às vezes, você pode querer ignorar os None resultados e obter uma lista
List<A> . Este é um caso de uso completamente diferente.

Exercício 6.11

Escreva uma função de nível de pacote chamada sequence que combine a


List<Option<A>> em um arquivo Option<List<A>> . Será um
Some<List<A>> se todos os valores na lista original forem Some instâncias ou
None<List<A>> se houver pelo menos um None na lista. Aqui está sua
assinatura:

fun <A> sequence(list: List<Option<A>>): Option<List<A>>

Observe que agora você deve usar o List definido no capítulo 5 e não o Kotlin
List .

Dica

Para encontrar o caminho, você pode testar a lista para ver se está vazia e, se não
estiver, fazer uma chamada recursiva para sequence . Então, lembrando dessa
foldRight and foldLeft recursão abstrata, você poderia usar uma dessas fun-
ções para implementar sequence .
Solução

Aqui está uma versão explicitamente recursiva que poderia ser usada se você ti-
vesse definido list.head() e list.tail() públicofunções em List (mas não
vai compilar porque você não tem):

fun <A> sequence(lista: List<Option<A>>): Option<List<A>> {


return if (list.isEmpty())
Opção(Lista())
senão
list.head().flatMap({ hh ->
sequence(list.tail()).map({ x -> x.cons(hh) }) })
}

As funções list.head() e list.tail() não deveriam existir porque essas fun-


ções possivelmente lançariam exceções quando chamadas em uma lista vazia.
Uma solução seria fazer essas funções retornarem um arquivo Option . Feliz-
mente, a sequence funçãotambém pode ser implementado usando foldRight e
map2 :

sequência <A> divertida(lista: Lista<Opção<A>>): Opção<Lista<A>> =


list.foldRight(Option(List())) { x ->
{ y: Opção<Lista<A>> -> map2(x, y) { a ->
{ b: List<A> -> b.cons(a) } }
}
}
Observe que o Kotlin infelizmente não é capaz de inferir tipos de parâmetros y e
arquivos b . (É aqui que o Kotlin não é tão poderoso quanto o Java!) Agora, consi-
dere o seguinte exemplo:

val parseWithRadix: (Int) -> (String) -> Int = { radix ->


{ string ->
Integer.parseInt(string, raiz)
}
}
val parse16 = hLift(parseWithRadix(16))
val lista = Lista("4", "5", "6", "7", "8", "9", "A", "B")
val result = sequence(list.map(parse16))
println(resultado)

Isso produz o resultado pretendido, mas é um tanto ineficiente porque a map fun-
çãoe a sequence funçãoambos invocam foldRight .

Exercício 6.12

Definir uma traverse funçãoque produz o mesmo resultado, mas invoca fol-
dRight apenas uma vez. Aqui está sua assinatura:

fun <A, B> traverse(lista: Lista<A> , f: (A) -> Opção<B>): Opção<Lista<B>>

Em seguida, implemente sequence em termos de traverse .


Dica

Não use recursão explicitamente. Prefira a foldRight função que abstrai a re-
cursão para você.

Solução

Primeiro defina a traverse função:

fun <A, B> percorrer(lista: Lista<A> , f: (A) -> Opção<B>): Opção<Lista<B>> =


list.foldRight(Option(List())) { x ->
{ y: Opção<Lista<B>> ->
map2(f(x), y) { a ->
{ b: Lista<B> ->
b.contras(a)
}
}
}
}

Então você pode redefinir a sequence funçãoem termos de traverse :

sequência <A> divertida(lista: Lista<Opção<A>>): Opção<Lista<A>> =


percorrer(lista) { x -> x }

6.4.7 Usando a Opção e quando fazê-lo

ComoEu disse no capítulo 2 e na introdução deste capítulo que Kotlin tem uma
maneira diferente de lidar com dados opcionais. Você pode então se perguntar
qual técnica deve usar - tipos anuláveis ​ou Option :

Os tipos anuláveis ​são úteis para uso interno em algumas funções, como gera-
dores, quando o retorno null indica a condição do terminal. Mas tais nulos
nunca devem vazar para fora de uma função; esses devem ser apenas para uso
local e nunca retornar, null exceto em funções locais.
Option é bom para dados verdadeiramente opcionais. Mas, geralmente, a au-
sência de dados é resultado de erros que os programadores tradicionais trata-
riam lançando e capturando exceções. Retornar None em vez de lançar uma
exceção é como capturar uma exceção e engoli-la silenciosamente. Pode não
ser um bilhão de dólareserro, mas ainda é um grande problema. Você apren-
derá no capítulo 7 como lidar com essa situação. Depois disso, dificilmente
você precisará do Option tipo de dados novamente. Mas não se preocupe;
tudo o que você aprendeu neste capítulo ainda será extremamente útil.

O Option tipo é a forma mais simples de um tipo de dado que você usará repeti-
damente. É um tipo parametrizado e vem com uma função para fazer um
Option<A> de um arquivo A . O Option tipo também tem uma flatMap fun-
çãoque podem ser usados ​para compor Option instâncias. Embora não seja útil
por si só, ele apresentou a você um conceito fundamental chamadomônada . Mas
List também tem as mesmas características. O importante é o que essas duas
classes têm em comum. Não se preocupe com o termo mônada ; não há nada a te-
mer. Veja-o como um padrão de design (embora seja muito maisdo queisto).
Resumo

Você precisa de uma maneira de representar dados opcionais, o que significa


dados que podem ou não estar presentes. O Some subtipo representa dados e o
None subtipo representa a ausência de dados.
Kotlin usa tipos anuláveis ​para representar dados opcionais. Eles protegem
você contra NullPointerException quando você usa tipos não anuláveis ​e
força você a lidar null ao usar tipos anuláveis.
O null ponteiro é a forma menos prática e perigosa de representar a ausência
de dados. Valores de sentinela e listas vazias são outras formas possíveis de re-
presentar a ausência de dados, mas não se compõe bem.
O Option tipo de dados é uma maneira melhor de representar dados
opcionais.
Você pode aplicar funções Option usando as funções map e flatMap de or-
dem superior, permitindo uma Option composição fácil. As funções que ope-
ram em valores podem ser levantadas para operar em Option instâncias.
List pode ser composto com Option . A List<Option> pode ser transfor-
mado em um Option<List> usando a sequence função.
Option instâncias podem ser comparadas por igualdade. As instâncias do sub-
tipo Some são iguais se seus valores agrupados forem iguais. Como há apenas
uma instância de None , todas as instâncias de None são iguais.
Embora Option possa representar o resultado de uma computação que pro-
duz uma exceção, todas as informações sobre a ocorrência da exceção são
perdidas.
1
 Tony Hoare, “Null References: The Billion Dollar Mistake” (QCon, 25 de agosto de
2009), http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-
Mistake-Tony-Hoare .

2  Consulte
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/Socket.html .

3
 A resposta de Brian Goetz à pergunta “Os getters do Java 8 devem retornar um
tipo opcional?” no Stackoverflow (12 de outubro de 2014),
https://stackoverflow.com/questions/26328555 .

Você também pode gostar