Cos’è un “contesto vincolato” in Scala?

Una delle nuove funzionalità di Scala 2.8 sono i limiti di contesto. Cos’è un contesto legato e dove è utile?

Naturalmente ho cercato per primo (e ho trovato ad esempio questo ) ma non sono riuscito a trovare alcuna informazione veramente chiara e dettagliata.

Hai trovato questo articolo ? Copre la nuova funzione legata al contesto, nel contesto dei miglioramenti degli array.

Generalmente, un parametro di tipo con un contesto associato è della forma [T: Bound] ; è espanso al parametro di tipo semplice T insieme a un parametro implicito di tipo Bound[T] .

Considera il tabulate del metodo che forma una matrice dai risultati dell’applicazione di una data funzione f su un intervallo di numeri da 0 fino a una determinata lunghezza. Fino a Scala 2.7, tabulate potrebbe essere scritto come segue:

 def tabulate[T](len: Int, f: Int => T) = { val xs = new Array[T](len) for (i <- 0 until len) xs(i) = f(i) xs } 

In Scala 2.8 questo non è più ansible, poiché le informazioni di runtime sono necessarie per creare la rappresentazione corretta di Array[T] . È necessario fornire queste informazioni passando ClassManifest[T] nel metodo come parametro implicito:

 def tabulate[T](len: Int, f: Int => T)(implicit m: ClassManifest[T]) = { val xs = new Array[T](len) for (i <- 0 until len) xs(i) = f(i) xs } 

Come una forma abbreviata, un vincolo di contesto può essere usato sul parametro tipo T , invece, dando:

 def tabulate[T: ClassManifest](len: Int, f: Int => T) = { val xs = new Array[T](len) for (i <- 0 until len) xs(i) = f(i) xs } 

La risposta di Robert copre i dettagli tecnici di Context Bounds. Ti darò la mia interpretazione del loro significato.

In Scala a View Bound ( A <% B ) cattura il concetto di 'può essere visto come' (mentre un limite superiore <: cattura il concetto di 'è un'). Un vincolo di contesto ( A : C ) dice 'ha a' su un tipo. Puoi leggere gli esempi sui manifesti come " T ha un Manifest ". L'esempio che hai collegato a Ordered vs Ordering illustra la differenza. Un metodo

 def example[T <% Ordered[T]](param: T) 

dice che il parametro può essere visto come Ordered . Confrontare con

 def example[T : Ordering](param: T) 

che dice che il parametro ha un Ordering associato.

In termini di utilizzo, è necessario un po 'di tempo prima che vengano stabilite le convenzioni, ma i limiti di contesto sono preferiti rispetto ai limiti di vista (i limiti di vista sono ora deprecati ). Un suggerimento è che un legame al contesto è preferito quando è necessario trasferire una definizione implicita da un ambito a un altro senza bisogno di fare riferimento direttamente ad esso (questo è certamente il caso del ClassManifest utilizzato per creare un array).

Un altro modo di pensare ai limiti di vista e al contesto è che i primi trasferimenti implicano conversioni dall'ambito del chiamante. Il secondo trasferisce oggetti impliciti dall'ambito del chiamante.

(Questa è una nota genetica. Leggi e capisci prima le altre risposte).

I limiti al contesto in effetti generalizzano i limiti di visualizzazione.

Quindi, dato questo codice express con un View Bound:

 scala> implicit def int2str(i: Int): String = i.toString int2str: (i: Int)String scala> def f1[T <% String](t: T) = 0 f1: [T](t: T)(implicit evidence$1: (T) => String)Int 

Questo potrebbe anche essere express con un Context Bound, con l’aiuto di un alias di tipo che rappresenta le funzioni da tipo F a tipo T

 scala> trait To[T] { type From[F] = F => T } defined trait To scala> def f2[T : To[String]#From](t: T) = 0 f2: [T](t: T)(implicit evidence$1: (T) => java.lang.String)Int scala> f2(1) res1: Int = 0 

Un contesto associato deve essere utilizzato con un costruttore di tipi di tipo * => * . Tuttavia il costruttore di tipi Function1 è di tipo (*, *) => * . L’uso dell’alias di tipo applica parzialmente il secondo parametro di tipo con il tipo String , restituendo un costruttore di tipi del tipo corretto da utilizzare come contesto.

Esiste una proposta per consentire di esprimere direttamente tipi parzialmente applicati in Scala, senza l’uso dell’alias di tipo all’interno di un tratto. Potresti quindi scrivere:

 def f3[T : [X](X => String)](t: T) = 0 

Questa è un’altra nota parentetica.

Come Ben ha sottolineato , un vincolo di contesto rappresenta un vincolo “ha-a” tra un parametro di tipo e una class di tipo. In altre parole, rappresenta un vincolo che esiste un valore implicito di una particolare class di tipi.

Quando si utilizza un vincolo di contesto, è spesso necessario far emergere quel valore implicito. Ad esempio, dato il vincolo T : Ordering , spesso occorrerà l’istanza di Ordering[T] che soddisfa il vincolo. Come dimostrato qui , è ansible accedere al valore implicito utilizzando il metodo implicitly o un metodo di context leggermente più utile:

 def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) = xs zip ys map { t => implicitly[Numeric[T]].times(t._1, t._2) } 

o

 def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) = xs zip ys map { t => context[T]().times(t._1, t._2) }