Eu posso ver nos documentos da API do Predef que são subclasses de um tipo de função genérica (From) => To, mas é tudo o que diz. Hum o que? Talvez haja documentação em algum lugar, mas os mecanismos de pesquisa não lidam com "nomes" como "<: <" muito bem, então não consegui encontrá-la.
Pergunta de acompanhamento: quando devo usar esses símbolos / classes descolados e por quê?
typeclass
es de Haskell realizam o trabalho desses operadores? Exemplocompare :: Ord a => a -> a -> Ordering
:? Estou tentando entender esse conceito da Scala em relação à sua contraparte Haskell.Respostas:
Estes são chamados de restrições de tipo generalizado . Eles permitem que você, dentro de uma classe ou característica com parâmetros de tipo, restrinja ainda mais um de seus parâmetros de tipo. Aqui está um exemplo:
O argumento implícito
evidence
é fornecido pelo compilador, seA
forString
. Você pode pensar nisso como uma prova de queA
éString
--o argumento em si não é importante, sabendo apenas que ele existe. [editar: bem, tecnicamente é realmente importante porque representa uma conversão implícita deA
paraString
, que é o que permite que você chamea.length
e não faça o compilador gritar com você]Agora eu posso usá-lo assim:
Mas se eu tentasse usá-lo com um
Foo
contendo algo diferente de umString
:Você pode ler esse erro como "não foi possível encontrar evidências de que Int == String" ... é como deveria ser!
getStringLength
está impondo mais restrições ao tipo doA
que o queFoo
geralmente exige; ou seja, você só pode chamargetStringLength
em aFoo[String]
. Essa restrição é imposta em tempo de compilação, o que é legal!<:<
e<%<
funcionam da mesma forma, mas com pequenas variações:A =:= B
significa que A deve ser exatamente BA <:< B
significa que A deve ser um subtipo de B (análogo à restrição de tipo simples<:
)A <%< B
significa que A deve ser visualizado como B, possivelmente via conversão implícita (análoga à restrição de tipo simples<%
)Esse trecho de @retronym é uma boa explicação de como esse tipo de coisa costumava ser realizado e como restrições de tipo generalizadas facilitam agora.
TERMO ADITIVO
Para responder à sua pergunta de acompanhamento, é certo que o exemplo que dei é bastante artificial e não é obviamente útil. Mas imagine usá-lo para definir algo como um
List.sumInts
método, que adiciona uma lista de números inteiros. Você não deseja permitir que esse método seja chamado em nenhum antigoList
, apenas aList[Int]
. No entanto, oList
construtor de tipos não pode ser tão restrito; você ainda deseja ter listas de strings, foos, barras e outras coisas. Portanto, ao colocar uma restrição de tipo generalizadasumInts
, você pode garantir que apenas esse método tenha uma restrição adicional de que ele só pode ser usado em aList[Int]
. Essencialmente, você está escrevendo um código de caso especial para certos tipos de listas.fonte
Manifest
, que você não mencionou.Manifest
são<:<
e>:>
única ... desde OP mencionado exatamente as 3 variedades de restrições de tipo generalizadas, eu estou supondo que é o que ele estava interessado.class =:=[From, To] extends From => To
, o que significa que um valor implícito do tipoFrom =:= To
é na verdade uma conversão implícita deFrom
paraTo
. Então, ao aceitar um parâmetro implícito do tipo,A =:= String
você está dizendo queA
pode ser implicitamente convertido emString
. Se você alterasse a ordem e tornasse o argumento implícito do tipoString =:= A
, não funcionaria, pois seria uma conversão implícita deString
paraA
.From =:= To
no escopo implica que você tem uma conversão implícitaFrom => To
, mas a implicação não é executada ao contrário; tendo uma conversão implícitaA => B
é que não significa que você tem uma instânciaA =:= B
.=:=
é uma classe abstracta selado definido emscala.Predef
, e tem apenas uma instância exposta ao público, o que está implícito, e é do tipoA =:= A
. Então, você tem a garantia de que um valor implícito do tipoA =:= B
testemunhas do fato de queA
eB
são iguais.Não é uma resposta completa (outros já responderam a isso), só queria observar o seguinte, o que talvez ajude a entender melhor a sintaxe: A maneira como você normalmente usa esses "operadores", como por exemplo no exemplo do pelotom:
faz uso da sintaxe de infixo alternativa do Scala para operadores de tipo .
Portanto,
A =:= String
é o mesmo que=:=[A, String]
(e=:=
é apenas uma classe ou característica com um nome atraente). Observe que essa sintaxe também funciona com classes "regulares", por exemplo, você pode escrever:como isso:
É semelhante às duas sintaxes para chamadas de método, o "normal" com
.
e()
e a sintaxe do operador.fonte
makes use of Scala's alternative infix syntax for type operators.
faltando totalmente esta explicação, sem que a coisa toda não faz sentidoLeia as outras respostas para entender quais são essas construções. Aqui é quando você deve usá-los. Você os utiliza quando precisa restringir um método apenas para tipos específicos.
Aqui está um exemplo. Suponha que você queira definir um par homogêneo, assim:
Agora você deseja adicionar um método
smaller
, como este:Isso só funciona se
T
for solicitado. Você pode restringir a classe inteira:Mas isso parece uma vergonha - pode haver usos para a classe quando
T
não for solicitado. Com uma restrição de tipo, você ainda pode definir osmaller
método:Não há problema em instanciar, digamos, a
Pair[File]
, contanto que você não o invoquesmaller
.No caso de
Option
, os implementadores queriam umorNull
método, mesmo que não faça sentidoOption[Int]
. Usando uma restrição de tipo, tudo está bem. Você pode usarorNull
umOption[String]
, e pode formar umOption[Int]
e usá-lo, desde que não o invoqueorNull
. Se você tentarSome(42).orNull
, você recebe a mensagem encantadorafonte
<:<
e acho que oOrdered
exemplo não é mais tão convincente, já que agora você prefere usar aOrdering
classe tipográfica do que aOrdered
característica. Algo como:def smaller(implicit ord: Ordering[T]) = if (ord.lt(first, second)) first else second
.Depende de onde eles estão sendo usados. Na maioria das vezes, quando usadas ao declarar tipos de parâmetros implícitos, são classes. Eles também podem ser objetos em casos raros. Finalmente, eles podem ser operadores em
Manifest
objetos. Eles são definidosscala.Predef
nos dois primeiros casos, embora não sejam particularmente bem documentados.Elas são destinadas a fornecer uma maneira de testar a relação entre as classes, assim como
<:
e<%
fazer, em situações em que este último não pode ser usado.Quanto à pergunta "quando devo usá-los?", A resposta é que você não deveria, a menos que saiba que deveria. :-) EDIT : Ok, ok, aqui estão alguns exemplos da biblioteca. Em
Either
, você tem:Em
Option
, você tem:Você encontrará outros exemplos nas coleções.
fonte
:-)
mais um desses? E eu concordaria que sua resposta para "Quando devo usá-los?" aplica-se a muitas coisas.