Qu'est-ce qu'un «contexte lié» dans Scala?

115

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

Bien sûr, j'ai cherché en premier (et trouvé par exemple ceci ) mais je n'ai pas trouvé d'informations vraiment claires et détaillées.

Jesper
la source
8
Consultez également ceci pour une visite de tous les types de limites: gist.github.com/257758/47f06f2f3ca47702b3a86c76a5479d096cb8c7ec
Arjan Blokzijl
2
Cette excellente réponse compare / contraste les limites du contexte et les limites d'affichage: stackoverflow.com/questions/4465948/…
Aaron Novstrup
C'est une très belle réponse stackoverflow.com/a/25250693/1586965
samthebest

Réponses:

107

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

En général, un paramètre de type avec un contexte lié est de la forme [T: Bound]; il est développé en paramètre de type simple Tavec un paramètre implicite de type Bound[T].

Considérons la méthode tabulatequi forme un tableau à partir des résultats de l'application d'une fonction f donnée sur une plage de nombres de 0 jusqu'à une longueur donnée. Jusqu'à Scala 2.7, le tableau pouvait être écrit 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 a 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 contexte lié peut être utilisé sur le paramètre type à la Tplace, 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
}
Robert Harvey
la source
145

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

Dans Scala, un View Bound ( A <% B) capture le concept de «peut être vu comme» (alors qu'une borne supérieure <:capture le concept de «est un»). Un contexte lié ( A : C) dit «a un» sur un type. Vous pouvez lire les exemples sur les manifestes comme " Ta un Manifest". L'exemple auquel vous avez lié about Orderedvs Orderingillustre la différence. Une méthode

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

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

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

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

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 maintenant obsolètes ). Une suggestion est qu'un contexte lié est préféré lorsque vous devez transférer une définition implicite d'une portée à une autre sans avoir besoin de s'y référer directement (c'est certainement le cas pour le ClassManifestutilisé pour créer un tableau).

Une autre façon de penser aux limites de vue et aux limites de contexte est que la première transfère les conversions implicites de la portée de l'appelant. Le second transfère les objets implicites de la portée de l'appelant.

Ben Lings
la source
2
"a un" plutôt que "est un" ou "vu comme" était le point de vue clé pour moi - je ne l'ai vu dans aucune autre explication. Avoir une version anglaise simple des opérateurs / fonctions autrement légèrement cryptiques rend beaucoup plus facile à absorber - merci!
DNA
1
@Ben Lings Qu'entendez-vous par ... "a un" à propos d'un type ...? Qu'est-ce qu'un type ?
jhegedus
1
@jhegedus Voici mon analyse: "à propos d'un type" signifie que A fait référence à un type. L'expression «a un» est souvent utilisée dans la conception orientée objet pour décrire les relations d'objet (par exemple, le client «a une» adresse). Mais ici, la relation «a un» est entre les types, pas les objets. C'est une analogie vague parce que la relation «a un» n'est pas inhérente ou universelle comme elle l'est dans la conception OO; un client a toujours une adresse mais pour le contexte lié un A n'a pas toujours un C. Au contraire, le contexte lié spécifie qu'une instance de C [A] doit être fournie implicitement.
jbyler
J'apprends Scala depuis un mois, et c'est la meilleure explication que j'ai vue ce mois-ci! Merci @Ben!
Lifu Huang
@Ben Lings: Merci, après avoir passé tant de temps à comprendre ce qui est lié au contexte, votre réponse est très utile. [A has aplus de sens pour moi]
Shankar
39

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

Les limites de contexte généralisent en fait 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 Contexte Bound, à l'aide d'un alias de type représentant des fonctions de type Fen 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 Function1est du genre (*, *) => *. L'utilisation de l'alias de type applique partiellement le second paramètre de type avec le type String, ce qui donne un constructeur de type du type correct à utiliser en tant que 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 
rétronyme
la source
Pourriez-vous expliquer la signification du #From dans la définition de f2? Je ne sais pas où le type F est construit (ai-je dit cela correctement?)
Collin
1
C'est ce qu'on appelle une projection de type, référençant un membre Fromde type du type To[String]. Nous ne fournissons pas d'argument de type à From, donc nous nous référons au constructeur de type, pas à un type. Ce constructeur de type est du bon type pour être utilisé en tant que contexte lié - * -> *. Cela limite le paramètre de type Ten exigeant un paramètre implicite de type To[String]#From[T]. Développez les alias de type, et voila, il vous reste Function1[String, T].
retronym
devrait-il être Function1 [T, String]?
ssanj le
18

Ceci est une autre note entre parenthèses.

Comme Ben l'a souligné , un contexte lié représente une contrainte "has-a" entre un paramètre de type et une classe de type. En d'autres termes, cela représente une contrainte qu'une valeur implicite d'une classe de type particulière existe.

Lors de l'utilisation d'un contexte lié, il faut souvent 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 implicitlyméthode ou une méthode légèrement plus utile context:

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) }
Aaron Novstrup
la source