web-dev-qa-db-fra.com

Qu'est-ce qu'un "contexte lié" à Scala?

L'une des nouvelles fonctionnalités de Scala 2.8 sont des limites de contexte. Qu'est-ce qu'une limite de contexte et où est-elle utile?

Bien sûr, j'ai cherché en premier (et trouvé par exemple this ) mais je n'ai trouvé aucune information vraiment claire et détaillée.

106
Jesper

Avez-vous trouvé cet article ? Il couvre la nouvelle fonctionnalité liée au contexte, dans le contexte des améliorations de tableau.

Généralement, un paramètre de type avec un contexte lié au contexte est de la forme [T: Bound]; il est étendu au paramètre de type brut T avec un paramètre implicite de type Bound[T].

Considérons la méthode tabulate qui forme un tableau à partir des résultats de l'application d'une fonction donnée f sur une plage de nombres allant de 0 jusqu'à une longueur donnée. Jusqu'à Scala 2.7, le tableau peut s'écrire comme suit:

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

Dans Scala 2.8 cela n'est plus possible, car les informations d'exécution sont nécessaires pour créer la bonne représentation de Array[T]. Il faut fournir ces informations en passant un ClassManifest[T] dans la méthode en tant que paramètre implicite:

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
}

En tant que forme abrégée, un lié au contexte peut être utilisé à la place sur le paramètre de type T, donnant:

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
}
100
Robert Harvey

La réponse de Robert couvre les détails techniques de Context Bounds. Je vais vous donner mon interprétation de leur signification.

Dans Scala a View Bound (A <% B) capture le concept de "peut être vu comme" (alors qu'une limite supérieure <: capture le concept de 'est un'). Un contexte lié (A : C) dit 'has a' à propos d'un type. Vous pouvez lire les exemples de manifestes comme "T a un Manifest". L'exemple que vous avez lié à environ Ordered vs Ordering illustre la différence. Une méthode

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

indique que le paramètre peut être vu comme un Ordered. Comparer avec

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

qui indique que le paramètre a un Ordering associé.

En termes d'utilisation, il a fallu un certain temps pour que les conventions soient établies, mais les limites de contexte sont préférées aux limites de vue ( les limites de vue sont désormais obsolètes ). Une suggestion est qu'une limite de contexte est préférée lorsque vous devez transférer une définition implicite d'une étendue à une autre sans avoir besoin de s'y référer directement (c'est certainement le cas pour le ClassManifest utilisé pour créer un tableau).

Une autre façon de penser les limites de vue et les limites de contexte est que le premier transfère des conversions implicites de la portée de l'appelant. La seconde transfère des objets implicites de la portée de l'appelant.

138
Ben Lings

(Ceci est une note entre parenthèses. Lisez et comprenez d'abord les autres réponses.)

Les limites de contexte généralisent réellement les limites de vue.

Donc, étant donné ce code exprimé avec 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

Cela pourrait également être exprimé avec un Context Bound, à l'aide d'un alias de type représentant les fonctions du type F au type 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 contexte lié doit être utilisé avec un constructeur de type de type * => *. Cependant, le constructeur de type Function1 est de nature (*, *) => *. L'utilisation de l'alias de type applique partiellement le deuxième paramètre de type avec le type String, produisant un constructeur de type du bon type à utiliser comme un contexte lié.

Il existe une proposition pour vous permettre d'exprimer directement des types partiellement appliqués dans Scala, sans utiliser l'alias de type à l'intérieur d'un trait. Vous pourriez alors écrire:

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

Ceci est une autre note entre parenthèses.

Comme Ben a souligné , un contexte lié représente une contrainte "has-a" entre un paramètre de type et une classe de type. Autrement dit, il représente une contrainte de l'existence d'une valeur implicite d'une classe de type particulière.

Lorsque vous utilisez un contexte lié, on a souvent besoin de faire apparaître cette valeur implicite. Par exemple, étant donné la contrainte T : Ordering, on aura souvent besoin de l'instance de Ordering[T] qui satisfait la contrainte. Comme démontré ici , il est possible d'accéder à la valeur implicite en utilisant la méthode implicitly ou une méthode context légèrement plus utile:

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

ou

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