O que são o contexto do Scala e os limites de exibição?

267

De uma maneira simples, quais são os limites de contexto e visualização e qual a diferença entre eles?

Alguns exemplos fáceis de seguir também seriam ótimos!

chrsan
fonte

Respostas:

477

Eu pensei que isso já havia sido solicitado, mas, se assim for, a pergunta não é aparente na barra "relacionada". Então aqui está:

O que é um limite de exibição?

Um view bound foi um mecanismo introduzido no Scala para permitir o uso de algum tipo A como se fosse algum tipo B. A sintaxe típica é esta:

def f[A <% B](a: A) = a.bMethod

Em outras palavras, Adeve ter uma conversão implícita Bdisponível, para que se possa chamar Bmétodos em um objeto do tipo A. O uso mais comum dos limites de exibição na biblioteca padrão (antes do Scala 2.8.0, de qualquer maneira) é com Ordered, assim:

def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b

Uma vez que se pode converter Aem um Ordered[A], e porque Ordered[A]define o método <(other: A): Boolean, que pode usar a expressão a < b.

Esteja ciente de que os limites de exibição foram descontinuados . Você deve evitá-los.

O que é um limite de contexto?

Os limites de contexto foram introduzidos no Scala 2.8.0 e normalmente são usados ​​com o chamado padrão de classe de tipo , um padrão de código que emula a funcionalidade fornecida pelas classes de tipo Haskell, embora de maneira mais detalhada.

Embora um limite de visualização possa ser usado com tipos simples (por exemplo A <% String), um limite de contexto requer um tipo parametrizado , como Ordered[A]acima, mas diferente String.

Um limite de contexto descreve um valor implícito , em vez da conversão implícita do limite de exibição . É usado para declarar que, para algum tipo A, existe um valor implícito do tipo B[A]disponível. A sintaxe é assim:

def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]

Isso é mais confuso do que a visualização vinculada, porque não está claro imediatamente como usá-lo. O exemplo comum de uso no Scala é este:

def f[A : ClassManifest](n: Int) = new Array[A](n)

Uma Arrayinicialização em um tipo parametrizado exige ClassManifestque esteja disponível, por razões misteriosas relacionadas ao apagamento de tipo e à natureza não apagável de matrizes.

Outro exemplo muito comum na biblioteca é um pouco mais complexo:

def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)

Aqui, implicitlyé usado para recuperar o valor implícito que queremos, um do tipo Ordering[A], cuja classe define o método compare(a: A, b: A): Int.

Veremos outra maneira de fazer isso abaixo.

Como os limites de exibição e de contexto são implementados?

Não deve surpreender que os limites de visualização e de contexto sejam implementados com parâmetros implícitos, dada sua definição. Na verdade, a sintaxe que mostrei são açúcares sintáticos para o que realmente acontece. Veja abaixo como eles descongelam:

def f[A <% B](a: A) = a.bMethod
def f[A](a: A)(implicit ev: A => B) = a.bMethod

def g[A : B](a: A) = h(a)
def g[A](a: A)(implicit ev: B[A]) = h(a)

Portanto, naturalmente, é possível escrevê-los em sua sintaxe completa, o que é especialmente útil para os limites do contexto:

def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)

Para que servem os limites de exibição?

Os limites de exibição são usados ​​principalmente para tirar proveito do padrão pimp my library , através do qual um "adiciona" métodos a uma classe existente, em situações em que você deseja retornar o tipo original de alguma forma. Se você não precisar retornar esse tipo de forma alguma, não precisará de uma visualização vinculada.

O exemplo clássico de uso vinculado à exibição é manipulação Ordered. Observe que Intnão existe Ordered, por exemplo, embora haja uma conversão implícita. O exemplo fornecido anteriormente precisa de uma visualização vinculada, pois retorna o tipo não convertido:

def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b

Este exemplo não funcionará sem limites de visualização. No entanto, se eu retornar outro tipo, não preciso mais de uma visualização vinculada:

def f[A](a: Ordered[A], b: A): Boolean = a < b

A conversão aqui (se necessário) acontece antes de eu passar o parâmetro para f, portanto f, não precisa saber sobre isso.

Além disso Ordered, o uso mais comum da biblioteca é manipulação Stringe Array, que são classes Java, como se fossem coleções Scala. Por exemplo:

def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b

Se alguém tentasse fazer isso sem exibir limites, o tipo de retorno de a Stringseria a WrappedString(Scala 2.8) e da mesma forma para Array.

O mesmo acontece mesmo se o tipo for usado apenas como um parâmetro de tipo do tipo de retorno:

def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted

Para que servem os limites de contexto?

Os limites de contexto são usados ​​principalmente no que ficou conhecido como padrão de classe de tipo , como uma referência às classes de tipo de Haskell. Basicamente, esse padrão implementa uma alternativa à herança, disponibilizando a funcionalidade por meio de um tipo de padrão implícito de adaptador.

O exemplo clássico é o Scala 2.8 Ordering, que foi substituído Orderedna biblioteca do Scala. O uso é:

def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b

Embora você normalmente veja isso escrito assim:

def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
    import ord.mkOrderingOps
    if (a < b) a else b
}

Que aproveitam algumas conversões implícitas internas Orderingque permitem o estilo tradicional do operador. Outro exemplo no Scala 2.8 é o Numeric:

def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)

Um exemplo mais complexo é o uso da nova coleção CanBuildFrom, mas já existe uma resposta muito longa sobre isso, então evitarei aqui. E, como mencionado anteriormente, existe o ClassManifestuso necessário para inicializar novas matrizes sem tipos concretos.

É muito mais provável que o contexto associado ao padrão de classe de tipo seja usado por suas próprias classes, pois elas permitem a separação de preocupações, enquanto os limites de exibição podem ser evitados em seu próprio código por um bom design (ele é usado principalmente para contornar o design de outra pessoa) )

Embora isso tenha sido possível há muito tempo, o uso de limites de contexto realmente decolou em 2010 e agora é encontrado até certo ponto na maioria das bibliotecas e estruturas mais importantes da Scala. O exemplo mais extremo de seu uso, porém, é a biblioteca Scalaz, que traz muito do poder de Haskell para Scala. Recomendo a leitura de padrões de classe para se familiarizar com todas as maneiras pelas quais ele pode ser usado.

EDITAR

Questões de interesse relacionadas:

Daniel C. Sobral
fonte
9
Muito obrigado Sei que isso já foi respondido antes e talvez não tenha lido atentamente o suficiente, mas sua explicação aqui é a mais clara que já vi. Então, obrigado novamente.
precisa
3
@chrsan Adicionei mais duas seções, entrando em mais detalhes sobre onde cada uma delas é usada.
Daniel C. Sobral
2
Eu acho que essa é uma excelente explicação. Gostaria de traduzir isso para o meu blog em alemão (dgronau.wordpress.com), se estiver tudo bem com você.
Landei
3
Esta é de longe a melhor e mais abrangente explicação sobre esse tópico que eu encontrei até agora. Muito obrigado mesmo!
FotNelton
2
Sooo, quando é seu livro Scala saindo, e onde posso comprá-lo :)
wfbarksdale