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 evidenceviene 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 Aa String, che è ciò che ti permette di chiamare a.lengthe 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 Fooqualcosa 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! getStringLengthimpone ulteriori restrizioni al tipo di Aquelle Foogeneralmente richieste; vale a dire, puoi solo invocare getStringLengthsu 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 <:< Bsignifica che A deve essere un sottotipo di B (analogo al vincolo di tipo semplice<: )
A <%< Bsignifica 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.sumIntsmetodo, 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 Listcostruttore 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.