Professional Documents
Culture Documents
14 Solving Common Problems Functionally - The Joy of Kotlin
14 Solving Common Problems Functionally - The Joy of Kotlin
funcionalmente
Este capítulocapas
Usando asserções
Novas tentativas automáticas para funções com falha ou aplicativos de efeito
Lendo arquivos de propriedades
Adaptando bibliotecas imperativas
Convertendo programas imperativos
Efeitos repetitivos
Agora você tem muitas ferramentas que podem facilitar sua vida como programa-
dor usando técnicas de programação seguras vindas do mundo da programação
funcional. Mas conhecer as ferramentas não é suficiente. Para se tornar eficiente
usando técnicas funcionais, você deve torná-las uma segunda natureza. Você pre-
cisa pensar funcionalmente. Assim como os programadores orientados a objetos
(OO) pensam em padrões, os programadores funcionais fazem o mesmo com as
funções.
Neste capítulo, você verá alguns problemas comuns que os programadores preci-
sam resolver na vida profissional diária. Você verá como pode abordar esses pro-
blemas de maneira diferente usando o paradigma funcional. Além de aprender a
resolver esses problemas cotidianos de maneira funcional, muitas vezes você pre-
cisará usar código imperativo. Mas qual é a melhor abordagem para usar esse có-
digo com programas funcionais? Começaremos com um programa imperativo e o
modificaremos para torná-lo mais eficiente e útil.
14.1 Afirmações e validação de dados
Esta função retorna um valor utilizável para qualquer entrada, exceto para 0,
para o qual retorna infinito. Como você provavelmente não pode fazer nada com
esse valor, talvez prefira tratá-lo de uma maneira específica. Na programação im-
perativa, você poderia escrever isto:
Este snippet usa asserções Java padrão, que estão disponíveis em Kotlin. Mas
como as asserções podem ser desativadas em tempo de execução, você pode que-
rer impedir que o programa seja executado com asserções desativadas usando o
seguinte:
if (!Thread.currentThread().javaClass.desiredAssertionStatus()) {
throw RuntimeException("Asserts devem ser habilitados!!!")
}
Para ser seguro, a função deve ser transformada em uma função total da seguinte
forma:
objeto complementar {
operador
divertido invocar(id: Int?,
primeiroNome: String?,
últimoNome: String?): Pessoa =
Pessoa(id, primeiroNome, sobrenome)
}
}
Esta função pode ser usada com dados extraídos de um banco de dados:
Nesse caso, convém validar os dados antes de chamar a função. Por exemplo, você
pode querer verificar se o ID é positivo e se o nome e o sobrenome não são nul-
l ou estão vazios e começam com uma letra maiúscula. Na programação impera-
tiva, você pode verificar isso testando cada condição antes de chamar a função
por meio do uso de funções de asserção:
Mas se você deseja que seus programas sejam seguros, não deve lançar exceções.
Em vez disso, você deve usar contextos especiais, como Result para tratamento
de erros. Esse tipo de validação é abstraído no Result tipo. Tudo o que você pre-
cisa fazer é escrever as funções de validação, o que significa que você precisa es-
crever funções e usar referências de função. As funções de validação genéricas
podem ser colocadas no nível do pacote da seguinte forma:
fun isPositive(i: Int?): Boolean = i != null && i > 0
objeto complementar {
fun of(id: Int, firstName: String, lastName: String) =
Result.of (::isPositive, id, "ID negativo").flatMap { validId ->
Result.of(::isValidName, firstName, "Nome inválido")
.flatMap { validFirstName ->
Result.of(::isValidName, lastName, "Sobrenome inválido")
.map { validLastName ->
Pessoa(validId, validFirstName, validLastName)
}
}
}
}
}
Mas você também pode simplificar as coisas abstraindo mais do processo em fun-
ções mais gerais:
fun assertPositive(i: Int, message: String): Result<Int> =
Result.of(::isPositive, i, message)
Impurofunções e efeitos muitas vezes devem ser repetidos se não forem bem-su-
cedidos na primeira chamada. Não ter sucesso geralmente significa lançar uma
exceção. Mas repetir uma função quando uma exceção é lançada é tedioso e su-
jeito a erros.
Imagine que você está lendo um valor de algum dispositivo que pode lançar
um IOException se o dispositivo não estiver pronto. Você pode tentar novamente
três vezes com um atraso de 100 ms entre cada nova tentativa. A solução impera-
tiva é algo como
var tentativas = 0
var resultado: String? = nulo
(0 .. 3).forEach rt@ { ②
tentar {
resultado = get("/meu/caminho")
return@rt ③
} catch(e: IOException) {
if (novas tentativas < 3) {
Thread.sleep(100) ④
novas tentativas += 1
} outro {
jogar e ⑤
}
}
}
println(resultado)
① Simula uma função que lança uma exceção 80% das vezes
② Usado como parâmetro da função forEach; rt@ indica onde você deseja retornar
de dentro da função
③ Conforme indicado por return@rt, o retorno de dentro da função usada como pa-
râmetro da função forEach é acionado quando a chamada para get é bem-
sucedida.
④ Se get lança uma exceção e as tentativas são menores que 3, uma nova tentativa é
tentada após aguardar 100 ms.
⑤ Se uma exceção for lançada e as novas tentativas não forem menores que 3, a ex-
ceção será lançada novamente.
O que você precisa é de uma retry funçãoque toma como parâmetros o seguinte:
Esta função não deve relançar nenhuma exceção. Em vez disso, ele deve retornar
um arquivo Result . Aqui está sua assinatura:
Você pode obter esse resultado de várias maneiras diferentes. Uma maneira seria
usar uma dobra com curto-circuito, dobrando o intervalo de 0 para ( número má-
ximo de tentativas ), mas escapando assim que uma chamada para a get fun-
çãosucesso. Você pode fazer isso facilmente usando um rótulo e a fold função
Kotlin padrãoem um intervalo Kotlin, como
fun <A, B> retry(f: (A) -> B, times: Int, delay: Long = 10) = rt@ { a: A ->
(0 .. vezes).fold("Não executado") { _, n ->
tentar {
print("Tente $n: ")
return@rt "Sucesso $n: ${f(a)}"
} catch (e: Exceção) {
Thread.sleep(atraso)
"${e.mensagem}"
}
}
}
Por outro lado, isso não funcionará com a range funçãovocê desenvolveu usando
sua List classeno exercício 8.19. Parece ser devido a um bug no Kotlin e, de qual-
quer forma, não compila. 1 Uma maneira de resolver o problema é usar corecur-
sion explícita, como você aprendeu no capítulo 4. Como sempre, isso implica defi-
nir uma função local auxiliar:
fun <A, B> repetir(f: (A) -> B,
vezes: Inter,
atraso: Longo = 10): (A) -> Resultado<B> {
fun retry(a: A, resultado: Resultado<B>,
e: Resultado<B>,
tms: Int): Resultado<B> =
result.orElse {
quando (tm) {
0 -> e
senão -> {
Thread.sleep(atraso)
// registra o número de tentativas
println("tente novamente ${vezes - tms}")
retry(a, Result.of { f(a) }, result, tms - 1)
}
}
}
return { a -> retry(a, Result.of { f(a) }, Result(), times - 1)}
}
Essa implementação usa uma função local que chama a si mesma recursivamente
com um número reduzido de novas tentativas até que esse número seja 0 ou a
chamada para a função seja bem- f sucedida. Você não pode tirar proveito da pa-
lavra- tailrec chave porque o Kotlin não vê essa função como recursiva. Isso
não é um problema, no entanto, porque o número de novas tentativas será baixo.
A println instrução é incluída apenas para permitir que você veja o que
acontece.
Com esta função, você pode transformar qualquer função em uma função com re-
petição automática. Você também pode usar esta função com efeitos puros (retor-
nando Unit ) como no seguinteexemplo:
qualquer que sejaformato que você usa, o processo é exatamente o mesmo: ler o
arquivo e lidar com qualquer exceção que possa surgir nesse processo. A pri-
meira coisa a fazer é ler o arquivo de propriedades e retornar um
Result<Properties> , conforme mostrado na listagem a seguir.
② A função Result.of retornará um sucesso se tudo correr bem ou uma falha se ocor-
rer uma exceção.
③ Isso permite obter uma referência à classe gerada, embora a função seja colocada
no nível do pacote.
Se o arquivo não for encontrado, a use função não lançará um IOException mas
retorna um null inputStream , causando um NullPointerException . Esta
função garante que o fluxo será fechado em qualquercaso.
properties.map { it.getProperty("nome") }
host=acme.org
porta=6666
nome=
temperatura = 71,3
preço=$45
lista=34,56,67,89
person=id:3;firstName:Jeanne;lastName:Doe
id=3
tipo=SERIAL
acme.org java.lang.NullPointerException
oproblema que você está enfrentando aqui é um bom exemplo do que nunca de-
veria acontecer. Kotlin depende da biblioteca padrão Java, então você tem certeza
de que tudo correrá conforme o esperado. Em particular, você espera que, se um
arquivo não for encontrado ou se não puder ser lido, você receberá uma extensão
IOException . Você pode até esperar ser informado sobre o caminho completo
do arquivo, pois um arquivo ausente geralmente é um arquivo que não está no lu-
gar certo. Uma boa mensagem de erro nesse caso seria “ I am looking for
file 'abc' in location 'xyz' but can't find it. ” Agora, observe o có-
digo do getResourceAsStream método Java:
Sim, é assim que o Java é escrito. A conclusão é que você nunca deve chamar um
método da biblioteca padrão Java sem olhar o código correspondente!
O Javadoc diz que o método retorna “Um fluxo de entrada para ler o recurso ou
null se o recurso não puder ser encontrado”. Isso significa que muitas coisas po-
dem dar errado. Pode ocorrer um `IOException erro se o arquivo não for en-
contrado ou se houver um problema durante a leitura. Ou o nome do arquivo
pode ser nulo. Ou o getResource métodopoderia lançar uma exceção ou retor-
nar null . (Veja o código desse método para ver o que quero dizer.)
O mínimo que você deve fazer é fornecer uma mensagem diferente para cada
caso. E, apesar do fato de que IOException é improvável que um seja lançado,
você ainda deve lidar com esse caso, bem como com o caso geral de uma exceção
inesperada, conforme mostrado na listagem a seguir.
}
}
} catch (e: IOException) {
Result.failure("IOException lendo o recurso de classpath $configFileName")
} catch (e: Exceção) {
Result.failure("Exceção: ${e.message} " +
" ao ler o recurso classpath $configFileName")
}
classe de dados Person(val id: Int, val firstName: String, val lastName: String)
id=três
propertyReader.readProperty("id")
.map(String::toInt)
.mapFailure("Formato inválido para propriedade \"id\": ???")
Mas você tem que escrever o nome da propriedade duas vezes, e seria útil substi-
tuir ??? pelo valor encontrado. (Isso não é possível porque o valor já está per-
dido.) Como você terá que analisar os valores de propriedade para todas as pro-
priedades não string, você deve abstrair isso dentro da PropertyReader classe.
Para fazer isso, primeiro renomeie a readProperty função:
Agora você não precisa se preocupar com erros ao converter para números
inteiros:
java.lang.IllegalStateException:
Valor inválido ao analisar a propriedade 'id' para Int: 'três'")
Vocêpode fazer o mesmo que você fez para números inteiros para outros tipos nu-
méricos, como Long ou Double . Você pode até fazer muito mais do que isso. Por
exemplo, você pode ler propriedades como listas como esta:
lista=34,56,67,89
Você só precisa adicionar uma função especializada para lidar com este caso. Para
isso, você pode usar a seguinte função para ler uma propriedade como uma lista
de números inteiros:
Este código usa a fromSeparated função definida na List classeque você en-
contrará no módulo com.fpinkotlin.common . Esse módulo está disponível no
código que acompanha este livro ( https://github.com/pysaumont/fpinkotlin ). Você
pode alterar o código para usar a função padrão Kotlin List alterando uma
linha:
Mas você pode fazer muito mais! Você pode ler uma propriedade como uma lista
de quaisquer valores numéricos fornecendo a função de conversão:
E você pode definir funções para todos os tipos de formatos de número em termos
de readAsList :
Vocêpode primeiro criar uma função para converter uma propriedade em qual-
quer tipo T , levando uma função de String para Result<T>:
em linha
fun <reified T: Enum<T>> readAsEnum(nome: String,
enumClass: Classe<T>): Resultado<T> {
val f: (String) -> Resultado<T> = {
tentar {
valor val = enumValueOf<T>(it)
Resultado(valor)
} catch (e: Exceção) {
Result.failure("Erro ao analisar a propriedade '$name': " +
"valor '$it' não pode ser analisado para ${enumClass.name}.")
}
}
return readAsType(f, nome)
}
Observe que isso reified significa que o tipo T deve estar acessível em tempo
de execução. Ao contrário de Java, Kotlin permite acessar parâmetros de tipo em
tempo de execução usando a palavra-chave reified , para que não seja apagado.
Essa possibilidade só é acessível em funções declaradas com inline , o que signi-
fica que o compilador pode copiar o código da função no site da chamada, em vez
de referenciar o código original. Isso aumenta o tamanho do código compilado.
tipo=SERIAL
e o seguinte enum
Até agora você leu propriedades como String , Int , Double , Boolean , listas
ou enums. Também pode ser interessante ler propriedades como objetos arbitrá-
rios. Para isso, você terá que escrever as propriedades do objeto em uma espécie
de forma serializada no arquivo de propriedades e depois carregar essas proprie-
dades e desserializareles.
Vocêpode usar a getAsType função para ler uma propriedade como qualquer
tipo. Por exemplo, você pode ler a seguinte propriedade para obter um Person :
person=id:3,firstName:Jane,lastName:Doe
Tudo o que você precisa fazer é fornecer uma função de String para
Result<Person> . Esta função deve ser capaz de criar um Person a partir da
string "id:3,firstName:Jane,lastName:Doe" . Para simplificar seu uso, você
pode criar uma readAsPerson função. Mas como é específico do tipo, você não
deve colocá-lo dentro da PropertyReader classe. Uma solução melhor é adicio-
nar uma função usando a PropertyReader e o nome da propriedade como argu-
mentos para a Person classe.
Existem várias maneiras de implementar essa função. Uma maneira é obter a pro-
priedade como uma lista e depois dividir cada elemento, colocando os pares
chave/valor em um mapa. Seria então fácil criar um a Person partir deste mapa.
Outra maneira seria criar um segundo PropertyReader que lê a string depois de
substituir as vírgulas por caracteres de nova linha. A listagem a seguir mostra a
Person classecom duas funções específicas para construir instâncias de uma
string de propriedade.
Listagem 14.4 Métodos que leem propriedades como objetos ou listas de objetos
objeto complementar {
fun readAsPerson(propertyName: String,
PropertyReader: PropertyReader): Resultado<Pessoa> {
val rString = propertyReader.readAsPropertyString(propertyName)
val rPropReader = rString.map { stringPropertyReader(it) }
return rPropReader.flatMap { readPerson(it) }
}
empregados=\
id:3;firstName:Jane;lastName:Doe,\
id:5;primeiroNome:Paul;últimoNome:Smith,\
id:8;primeiroNome:Mary;últimoNome:Winston
class PropertyReader(
private val properties: Result<Properties>, ①
private val source: String) { ②
...
objeto complementar {
privado
fun readPropertiesFromFile(configFileName: String):
Resultado<Propriedades> = ⑤
tentar {
MethodHandles.lookup().lookupClass()
.getResourceAsStream(configFileName)
.use { inputStream ->
quando (inputStream) {
null -> Result.failure(
"Arquivo $configFileName não encontrado no classpath")
senão -> Propriedades().let {
it.load(inputStream)
Resultado(isso)
}
}
}
} catch (e: IOException) {
Resultado.falha(
"IOException lendo o recurso de caminho de classe $configFileName")
} catch (e: Exceção) {
Result.failure("Exceção: ${e.message}lendo classpath"
+ "recurso $configFileName")
}
diversão privada readPropertiesFromString(propString: String):
Resultado<Propriedades> = ⑥
tentar {
StringReader(propString).use { leitor ->
val propriedades = Propriedades()
properties.load(leitor)
Resultado(propriedades)
}
} catch (e: Exceção) {
Result.failure("Exceção ao ler string de propriedades " +
"$propString: ${e.mensagem}")
}
Você pode fazer o mesmo para arquivos de propriedades XML ou para outros for-
matos, como JSONouYAML.
Escritanovos programas funcionais para qualquer tarefa que você tenha que rea-
lizar é empolgante, mas a maioria dos desenvolvedores geralmente não tem
tempo para isso. Freqüentemente, você desejará usar programas imperativos
existentes em seu próprio código. Este é o caso toda vez que você deseja usar uma
biblioteca existente.
Você pode achar mais interessante começar do zero e construir uma solução com-
pletamente nova e 100% funcional. Mas você tem que ser realista. Geralmente,
você não tem tempo ou orçamento para fazer isso e terá que usar bibliotecas não
funcionais existentes cheias de null exceções, lançamento de exceções e funções
impuras que alteram seus parâmetros no mundo exterior.
Como você logo descobrirá, quando estiver familiarizado com as técnicas funcio-
nais, será difícil voltar ao antigo estilo de codificação imperativo. A solução geral-
mente é construir um wrapper funcional fino em torno dessas bibliotecas impera-
tivas. Como exemplo, vamos examinar uma biblioteca comum para leitura de ar-
quivos XML, JDOM 2.0.6. Esta é a biblioteca Java mais comumente usada para esta
tarefa e é perfeitamente utilizável com Kotlin.
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import java.io.File;
importar java.io.IOException;
importar java.util.List;
Este programa Java pode ser facilmente reescrito em Kotlin imperativo, como
você pode ver na listagem a seguir.
Listagem 14.7 Lendo dados XML com JDOM: versão imperativa do Kotlin
importar org.jdom2.JDOMException
import org.jdom2.input.SAXBuilder
importar java.io.File
importar java.io.IOException
/**
* Não testável, lança exceções.
*/
fun main(args: Array<String>) {
tentar {
val document = builder.build(xmlFile)
val rootNode = document.rootElement
lista val = rootNode.getChildren("staff")
lista.paraCada {
println("Nome: ${it.getChildText("firstName")}")
println("\tSobrenome: ${it.getChildText("lastName")}")
println("\tEmail: ${it.getChildText("email")}")
println("\tSalário: ${it.getChildText("salário")}")
}
} catch (io: IOException) {
println(io.message)
} catch (e: JDOMException) {
println(e.mensagem)
}
}
O arquivo de dados usado com este exemplo é mostrado na listagem a seguir.
<?xml versão="1.0"?>
<empresa>
<pessoal>
<firstName>Paulo</firstName>
<lastName>Smith</lastName>
<email>paul.smith@acme.com</email>
<salário>100000</salário>
</staff>
<pessoal>
<firstName>Maria</firstName>
<lastName>Colson</lastName>
<email>mary.colson@acme.com</email>
<salário>200000</salário>
</staff>
</empresa>
A terceira função recebe uma lista de elementos como seu argumento e retorna
uma lista de representações de string desses elementos. Isso é implementado por
uma função com a seguinte assinatura:
Eventualmente você precisará aplicar um efeito aos dados, então você terá que
defini-lo com a seguinte assinatura:
Essa decomposição em funções não parece muito diferente do que você poderia
fazer na programação imperativa. Afinal, é uma boa prática decompor programas
imperativos em funções com uma única responsabilidade cada. Mas é mais dife-
rente do que pode parecer.
Da mesma forma, o caminho do arquivo pode ser retornado pelo mesmo tipo de
função:
Apesaros tipos de argumento e retorno não correspondem, você pode compor es-
sas funções facilmente usando um padrão de compreensão como este:
resultado.forEach(onSuccess =
{ processList(it) }, onFailure = { it.printStackTrace() })
Esta versão funcional do programa é muito mais limpa e totalmente testável. Ou,
pelo menos, será quando você tiver implementado todos osfunções.
Implementando as funções
Então você precisa implementar a readFile2String função. Aqui está uma das
muitas implementações possíveis:
③ O * no início da expressão indica que o array resultante deve ser usado como um
vararg e não como um único objeto.
Você primeiro pega IOException (queé improvável que seja lançado porque
você está lendo de uma string) e JDOMException , ambos são exceções verifica-
das e retornam uma falha com a mensagem de erro correspondente. Mas olhando
para o código JDOM (lembre-se de que ninguém deve chamar um método de bibli-
oteca sem antes ver como ele é implementado), você vê que o código pode lançar
um IllegalStateException ou um NullPointerException . Mais uma vez,
você tem que pegar Exception . a toStringList funçãomapeia a lista para
uma função responsável pela conversão:
Seu programa agora é muito mais modular e mais testável, e suas partes são reuti-
lizáveis. Mas você ainda pode fazer melhor. Você ainda está usando quatro ele-
mentos não funcionais:
O caminho do arquivo
O nome do elemento raiz
O formato usado para converter os elementos em string
O efeito aplicado ao resultado
Por não funcional, quero dizer que esses elementos são acessados diretamente da
implementação de suas funções, tornando-os transparentes de forma não referen-
cial. Para tornar seu programa totalmente funcional, você deve tornar esses ele-
mentos parâmetros de seu programa.
Agora seu programa pode ser uma função pura, recebendo quatro argumentos e
retornando um novo programa executável (não funcional) como resultado. Esta
versão do programa é representada na listagem a seguir.
import com.fpinkotlin.common.List
import com.fpinkotlin.common.Result
import org.jdom2.Element
importar org.jdom2.JDOMException
import org.jdom2.input.SAXBuilder
importar java.io.File
importar java.io.FileInputStream
importar java.io.IOException
importar java.io.StringReader
fun readXmlFile(
sPath: () -> Resultado<String>,
sRootName: () -> Resultado<String>,
formato: Pair<String, List<String>>,
efeito: (List<String>) -> Unit): () -> Unit { ①
val path = sPath() ②
val rDoc = path.flatMap(::readFile2String)
val rRoot = sRootName() ③
val resultado = rDoc.flatMap { doc ->
rRoot.flatMap { rootElementName ->
readDocument(rootElementName, doc) }
.map { lista -> toStringList(lista, formato) }
}
Retorna {
result.forEach(onSuccess = { efeito(isso) },
onFailure = { it.printStackTrace() }) ④
}
}
① O caminho e o nome do elemento raiz agora são recebidos como funções constan-
tes. O formato inclui os nomes dos parâmetros e a função tem um efeito do tipo
(List<String>) -> Unit como um parâmetro adicional.
Neste ponto, você pode testar este programa com o código do cliente mostrado na
listagem a seguir.
import com.fpinkotlin.common.List
import com.fpinkotlin.common.Result
Este programa não é ideal porque você não lidou com o possível erro que pode
surgir de nomes de elementos inválidos. Por exemplo, se você usar um nome de
elemento errado como em
<empresa>
<pessoal>
<firstname></firstname>
<lastName>Smith</lastName>
<email>paul.smith@acme.com</email>
<salário>100000</salário>
</staff>
<pessoal>
<firstname>Maria</firstname>
<lastName>Colson</lastName>
<email>mary.colson@acme.com</email>
<salário>200000</salário>
</staff>
</empresa>
Nome: nulo
Apelido: Smith
e-mail: paul.smith@acme.com
Salário: 100.000
Nome: nulo
Apelido: Colson
e-mail: mary.colson@acme.com
Salário: 200.000
Você pode adivinhar qual é o erro vendo que todos os primeiros nomes são nu-
los. Seria melhor substituir a palavra null por uma mensagem explícita contendo
o nome do elemento incorreto. Um problema mais importante é que, se você es-
quecer um dos nomes de elemento na lista, obterá uma exceção da
String.format função devido ao seguinte código:
Nesse código, a matriz de parâmetros terá apenas três elementos em vez dos qua-
tro esperados. Mas será difícil localizar a origem do erro da exceçãovestígio. Na
verdade, a verdadeira causa do problema é que você retirou todos os dados espe-
cíficos da readXmlFile função, como o nome do elemento raiz, o caminho do ar-
quivo e o efeito a ser aplicado, mas a processElement funçãoainda é específico
para o caso de uso de negócios do cliente. A readXmlFile função permite apenas
ler todos os elementos que são filhos diretos do elemento raiz, reunindo alguns
dos valores de seus elementos filhos diretos (aqueles cujos nomes são passados
junto com o formato).
objeto complementar {
Como você vê, as mudanças são mínimas. A classe do cliente também deve ser
modificada:
Com essas mudanças, agora é impossível mudar a ordem dos argumentos sem ser
avisado pelocompilador.
odois problemas restantes podem ser resolvidos com uma única alteração: passar
a função de processamento de elementos como um parâmetro para o readXml-
File método. Dessa forma, essa função tem uma única tarefa: lê a lista de ele-
mentos de primeiro nível do arquivo, aplica uma função configurável a essa lista
e retorna o resultado. A principal diferença é que a função não produzirá mais
uma lista de strings e aplicará um efeito de string. Você precisará tornar a função
genérica. Isso significa apenas as seguintes alterações:
① A função é genérica.
O programa cliente agora pode ser modificado de acordo. Isso evita que você use
o Pair truque para passar a string de formato e a lista de nomes de parâmetros:
O processList efeito não mudou. Agora cabe ao cliente fornecer uma função
para converter um elemento e um efeito para aplicar a esteelemento.
A única alteração adicional a ser feita é para lidar com o erro que pode ocorrer na
processElement função. A melhor abordagem é mais uma vez examinar o có-
digo do getChildText métodode JDOM. Este método é implementado da se-
guinte forma:
/**
* Retorna o conteúdo textual do elemento filho nomeado ou nulo se
* não existe tal criança. Este método é uma conveniência porque chamar
* <code>getChild().getText()</code> pode gerar uma NullPointerException.
*
* @param cname o nome da criança
* @return conteúdo de texto para o filho nomeado ou nulo se não houver tal filho
*/
public String getChildText(string final cname) {
elemento final filho = getChild(cname);
if (filho == nulo) {
retornar nulo;
}
return filho.getText();
}
③ Se o valor retornado for nulo, ele será substituído por uma mensagem de erro
explícita.
Agora, a maioria dos erros potenciais são tratados de forma funcional, mas nem
todos os erros podem ser tratados funcionalmente. Como eu disse anteriormente,
as exceções que são lançadas pelo efeito passado para o readXmlFile méto-
donão pode ser tratado desta forma. Essas são exceções lançadas pelo programa
retornado pela função. Quando a função retorna o programa, ele ainda não foi
executado. Essas exceções devem ser capturadas durante a execução do programa
resultante, porexemplo:
A solução para este problema é fácil. Esses fechamentos são argumentos implíci-
tos adicionais da processElement função. A indicação do problema é que, ao
contrárioa definição da função, esses dois valores devem fazer parte do programa
cliente, ou seja, a main função.
val elementNames =
List("firstName", "lastName", "email", "salário")
Resumo
1 Leia sobre este bug do Kotlin, problema KT-24055, “Uso incorreto de rótulo com
retorno local causa exceção interna no compilador” em
https://youtrack.jetbrains.com/issue/KT-24055 .