Tipagem estrutural e classes de tipo em Scala

Vamos começar com um exemplo concreto: você deve conhecer a interface Closeable em Java. Ele permite que você gerencie recursos que podem ser fechados de forma concisa. O problema com essa solução é que ela só funcionará com tipos que já a implementam. E a lista é bem longa, mas não inclui aquela nova classe brilhante que você criou ontema classe daquela brilhante biblioteca de código aberto que você usa; e, infelizmente, você não pode modificar essa classe.

Gostaríamos de poder usar a digitação de pato : se houver um método de fechamento, ele pode ser fechado. No Scala, você pode conseguir isso com tipagem estrutural:

def withResource[T](r: {def close(): Unit}, f: => T): T = {
try {
f

} finally {
// let me simplify things here by assuming close is not throwing exceptions itself
r
.close()
}
}

Aqui, o tipo de r pode ser qualquer tipo que tenha um método de fechamento que não receba parâmetros e retorne unidade. O interessante sobre isso é que você pode usar a função withResource com tipos para os quais não a projetou, desde que tenham a estrutura que você definiu. Daí o nome.

O problema com esta solução é que ela tem implicações de tempo de execução, uma vez que Scala usará introspecção em tempo de execução.

Mas nós podemos fazer melhor. Digite classes de tipo:

trait Closeable[R] {
def close(r: R): Unit
}

object Closeable {

implicit object SqlConnectionIsCloseable extends Closeable[Connection] {
def close(conn: Connection) = conn.close()
}

}

def withResource[R:Closeable, T](r: R, f: => T): T = {
try {
f

} finally {
implicitly
[Closeable[R]].close(r)
}
}

Closeable é uma classe de tipo: define uma classe de tipos que inclui todos os tipos que podem ser fechados, assim como o tipo estrutural que usamos antes. Mas, então, você adiciona um tipo à classe definindo um objeto implícito que diz ao compilador como esse tipo pertence à classe de tipo e como o método close é implementado para essa classe específica.

Quando você define um método que leva um parâmetro que “pode ​​ser fechado” (r no exemplo), você simplesmente usa um limite de contexto informando ao compilador que deve haver um objeto implícito cujo tipo é Closeable [R], onde R é o tipo de o parâmetro. Então, implicitamente [Closeable [R]] é um truque para obter essa instância.

Agora você pode estender a solução apenas criando novos objetos implícitos para novos tipos que você descobrir que podem ser fechados de alguma forma, mesmo se o método não for denominado close.