No Scala, podemos usar pelo menos dois métodos para adaptar os tipos existentes ou novos. Suponha que queremos expressar que algo pode ser quantificado usando um Int
. Podemos definir o seguinte traço.
Conversão implícita
trait Quantifiable{ def quantify: Int }
E então podemos usar conversões implícitas para quantificar, por exemplo, Strings e Lists.
implicit def string2quant(s: String) = new Quantifiable{
def quantify = s.size
}
implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{
val quantify = l.size
}
Depois de importá-los, podemos chamar o método quantify
em strings e listas. Observe que a lista quantificável armazena seu comprimento, portanto, evita o percurso caro da lista em chamadas subsequentes para quantify
.
Classes de tipo
Uma alternativa é definir uma "testemunha" Quantified[A]
que afirma que algum tipo A
pode ser quantificado.
trait Quantified[A] { def quantify(a: A): Int }
Em seguida, fornecemos instâncias desse tipo de classe para String
e em List
algum lugar.
implicit val stringQuantifiable = new Quantified[String] {
def quantify(s: String) = s.size
}
E se escrevermos um método que precisa quantificar seus argumentos, escrevemos:
def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) =
as.map(ev.quantify).sum
Ou usando a sintaxe associada ao contexto:
def sumQuantities[A: Quantified](as: List[A]) =
as.map(implicitly[Quantified[A]].quantify).sum
Mas quando usar qual método?
Agora vem a pergunta. Como posso decidir entre esses dois conceitos?
O que percebi até agora.
classes de tipo
- classes de tipo permitem a boa sintaxe ligada ao contexto
- com classes de tipo, não crio um novo objeto wrapper em cada uso
- a sintaxe ligada ao contexto não funciona mais se a classe de tipo tiver vários parâmetros de tipo; imagine que eu queira quantificar as coisas não apenas com números inteiros, mas com valores de algum tipo geral
T
. Eu gostaria de criar uma classe de tipoQuantified[A,T]
conversão implícita
- como eu crio um novo objeto, posso armazenar valores em cache ou calcular uma representação melhor; mas devo evitar isso, visto que pode acontecer várias vezes e uma conversão explícita provavelmente seria invocada apenas uma vez?
O que espero de uma resposta
Apresente um (ou mais) casos de uso em que a diferença entre os dois conceitos seja importante e explique por que eu preferiria um em vez do outro. Também explicar a essência dos dois conceitos e sua relação entre si seria bom, mesmo sem exemplo.
fonte
size
de uma lista em um valor e diz que isso evita o percurso caro da lista em chamadas subsequentes para quantificar, mas em cada chamada paraquantify
olist2quantifiable
é acionado tudo de novo, reinstanciandoQuantifiable
e recalculando aquantify
propriedade. O que estou dizendo é que, na verdade, não há como armazenar em cache os resultados com conversões implícitas.Respostas:
Embora eu não queira duplicar meu material do Scala In Depth , acho que vale a pena notar que as classes de tipo / características de tipo são infinitamente mais flexíveis.
tem a capacidade de pesquisar seu ambiente local por uma classe de tipo padrão. No entanto, posso substituir o comportamento padrão a qualquer momento por uma das seguintes maneiras:
Aqui está um exemplo:
Isso torna as classes de tipo infinitamente mais flexíveis. Outra coisa é que as classes / características de tipo suportam melhor a pesquisa implícita .
Em seu primeiro exemplo, se você usar uma visualização implícita, o compilador fará uma pesquisa implícita por:
Que examinará
Function1
o objeto companheiro de e oInt
objeto companheiro.Observe que não
Quantifiable
está em nenhum lugar da pesquisa implícita. Isso significa que você deve colocar a visão implícita em um objeto de pacote ou importá-la para o escopo. É mais trabalhoso lembrar o que está acontecendo.Por outro lado, uma classe de tipo é explícita . Você vê o que está procurando na assinatura do método. Você também tem uma pesquisa implícita de
que examinará
Quantifiable
o objeto companheiro de eInt
o objeto companheiro de. O que significa que você pode fornecer padrões e novos tipos (como umaMyString
classe) podem fornecer um padrão em seu objeto companheiro e ele será pesquisado implicitamente.Em geral, eu uso classes de tipo. Eles são infinitamente mais flexíveis para o exemplo inicial. O único lugar em que uso conversões implícitas é quando uso uma camada de API entre um wrapper Scala e uma biblioteca Java, e mesmo isso pode ser 'perigoso' se você não tomar cuidado.
fonte
Um critério que pode entrar em jogo é como você deseja que o novo recurso "pareça"; usando conversões implícitas, você pode fazer parecer que é apenas outro método:
... ao usar classes de tipo, sempre parecerá que você está chamando uma função externa:
Uma coisa que você pode alcançar com classes de tipo e não com conversões implícitas é adicionar propriedades a um tipo , em vez de uma instância de um tipo. Você pode acessar essas propriedades mesmo quando não tiver uma instância do tipo disponível. Um exemplo canônico seria:
Este exemplo também mostra como os conceitos estão intimamente relacionados: as classes de tipo não seriam tão úteis se não houvesse um mecanismo para produzir um número infinito de suas instâncias; sem o
implicit
método (não é uma conversão, admito), eu poderia apenas ter finitamente muitos tipos com aDefault
propriedade.fonte
default
para futuros leitores.Você pode pensar na diferença entre as duas técnicas por analogia ao aplicativo de função, apenas com um wrapper nomeado. Por exemplo:
Uma instância do primeiro encapsula uma função do tipo
A => Int
, enquanto uma instância do último já foi aplicada a umA
. Você poderia continuar o padrão ...assim, você pode pensar em
Foo1[B]
uma aplicação parcial deFoo2[A, B]
a algumaA
instância. Um grande exemplo disso foi escrito por Miles Sabin como "Dependências funcionais no Scala" .Então, o que quero dizer é que, em princípio:
fonte