Questi sono chiamati vincoli di tipo generalizzati . Consentono all'utente, all'interno di una classe o caratteristica parametrizzata di tipo, di limitare ulteriormente uno dei suoi parametri di tipo. Ecco un esempio:
case class Foo[A](a:A) { // 'A' can be substituted with any type
// getStringLength can only be used if this is a Foo[String]
def getStringLength(implicit evidence: A =:= String) = a.length
}
L'argomento implicito evidence
viene fornito dal compilatore, se A
è String
. Si può pensare ad esso come un prova che A
è String
--il argomento in sé non è importante, ma solo sapendo che esiste. [modifica: beh, tecnicamente in realtà è importante perché rappresenta una conversione implicita da A
a String
, che è ciò che ti permette di chiamare a.length
e non avere l'urlo del compilatore contro di te]
Ora posso usarlo in questo modo:
scala> Foo("blah").getStringLength
res6: Int = 4
Ma se ho provato ad usarlo con un Foo
qualcosa di diverso da un String
:
scala> Foo(123).getStringLength
<console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]
Puoi leggere quell'errore come "impossibile trovare la prova che Int == String" ... è come dovrebbe essere! getStringLength
impone ulteriori restrizioni al tipo di A
quelle Foo
generalmente richieste; vale a dire, puoi solo invocare getStringLength
su a Foo[String]
. Questo vincolo viene applicato in fase di compilazione, il che è fantastico!
<:<
e <%<
funziona in modo simile, ma con lievi variazioni:
A =:= B
significa che A deve essere esattamente B
A <:< B
significa che A deve essere un sottotipo di B (analogo al vincolo di tipo semplice<:
)
A <%< B
significa che A deve essere visualizzabile come B, possibilmente tramite conversione implicita (analogo al vincolo di tipo semplice <%
)
Questo frammento di @retronym è una buona spiegazione di come questo genere di cose veniva realizzato e di come i vincoli di tipo generalizzati ora lo rendono più semplice.
ADDENDUM
Per rispondere alla tua domanda di follow-up, è vero che l'esempio che ho dato è piuttosto elaborato e ovviamente non utile. Ma immagina di usarlo per definire qualcosa di simile a un List.sumInts
metodo, che aggiunge un elenco di numeri interi. Non vuoi permettere che questo metodo sia invocato su nessun vecchio List
, solo a List[Int]
. Tuttavia, il List
costruttore del tipo non può essere così limitato; vuoi comunque essere in grado di avere elenchi di stringhe, foo, barre e quant'altro. Pertanto, impostando un vincolo di tipo generalizzato su sumInts
, è possibile assicurarsi che proprio quel metodo abbia un vincolo aggiuntivo che può essere utilizzato solo su un List[Int]
. In sostanza stai scrivendo un codice per casi speciali per alcuni tipi di elenchi.