Che cos'è un “contesto vincolato” in Scala?


115

Una delle nuove funzionalità di Scala 2.8 sono i limiti di contesto. Che cos'è un contesto vincolato e dove è utile?

Ovviamente ho cercato prima (e ho trovato ad esempio questo ) ma non sono riuscito a trovare informazioni veramente chiare e dettagliate.


8
dai un'occhiata anche a questo per un tour di tutti i tipi di limiti: gist.github.com/257758/47f06f2f3ca47702b3a86c76a5479d096cb8c7ec
Arjan Blokzijl

2
Questa risposta eccellente confronta limiti contrasti / contesto e vista limiti: stackoverflow.com/questions/4465948/...
Aaron Novstrup

Questa è una bella risposta stackoverflow.com/a/25250693/1586965
samthebest

Risposte:


107

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

In genere, un parametro di tipo con un contesto vincolato è della forma [T: Bound]; viene espanso in Tun parametro di tipo semplice insieme a un parametro implicito di tipo Bound[T].

Considera il metodo tabulateche forma un array dai risultati dell'applicazione di una data funzione f su un intervallo di numeri da 0 fino a una data lunghezza. Fino a Scala 2.7, la tabulazione poteva essere scritta 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ù possibile, perché le informazioni di runtime sono necessarie per creare la giusta rappresentazione di Array[T]. È necessario fornire queste informazioni passando a 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 forma abbreviata, è possibile utilizzare invece un vincolo di contesto sul parametro di tipo T, fornendo:

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
}

145

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

In Scala una vista Bound ( A <% B) cattura il concetto di "può essere visto come" (mentre un limite superiore <:cattura il concetto di "è un"). Un context bound ( A : C) dice "ha un" su un tipo. Puoi leggere gli esempi sui manifesti come " Tha un Manifest". L'esempio a cui hai collegato about Orderedvs Orderingillustra la differenza. Un metodo

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

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

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

che dice che il parametro ha un associato Ordering.

In termini di utilizzo, è stato necessario un po 'di tempo prima che le convenzioni venissero stabilite, ma i limiti del contesto sono preferiti rispetto ai limiti della vista (i limiti della vista sono ora deprecati ). Un suggerimento è che è preferibile un vincolo di contesto quando è necessario trasferire una definizione implicita da un ambito a un altro senza dovervi fare riferimento direttamente (questo è certamente il caso ClassManifestdell'usato per creare un array).

Un altro modo di pensare ai limiti della vista e del contesto è che il primo trasferisce le conversioni implicite dall'ambito del chiamante. Il secondo trasferisce gli oggetti impliciti dall'ambito del chiamante.


2
"ha un" piuttosto che "è un" o "visto come" è stata l'intuizione chiave per me - non visto in altre spiegazioni. Avere una versione in inglese semplice degli operatori / funzioni altrimenti leggermente criptici rende molto più facile assorbire - grazie!
DNA

1
@ Ben Lings Cosa intendi con .... "ha un" su un tipo ...? Che cos'è un tipo ?
jhegedus

1
@ jhegedus Ecco la mia analisi: "about a type" significa che A si riferisce a un tipo. La frase "ha un" viene spesso utilizzata nella progettazione orientata agli oggetti per descrivere le relazioni tra oggetti (ad esempio, il cliente "ha un" indirizzo). Ma qui la relazione "ha un" è tra tipi, non oggetti. È un'analogia sciolta perché la relazione "ha un" non è intrinseca o universale come è nel design OO; un cliente ha sempre un indirizzo ma per il contesto vincolato una A non ha sempre una C. Piuttosto, il contesto vincolato specifica che un'istanza di C [A] deve essere fornita implicitamente.
jbyler

Sto imparando Scala da un mese e questa è la migliore spiegazione che ho visto in questo mese! Grazie @ Ben!
Lifu Huang

@ Ben Lings: Grazie, dopo aver speso così tanto tempo per capire cosa è vincolato al contesto, la tua risposta è molto utile. [ has aHa più senso per me]
Shankar

39

(Questa è una nota tra parentesi. Leggi e comprendi prima le altre risposte.)

I limiti del contesto in realtà generalizzano i limiti della vista.

Quindi, dato questo codice espresso con un limite di visualizzazione:

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 espresso con un Context Bound, con l'aiuto di un alias di tipo che rappresenta le funzioni da tipo Fa 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 tipo di tipo * => *. Tuttavia il costruttore Function1del tipo è di tipo (*, *) => *. L'utilizzo dell'alias di tipo applica parzialmente il secondo parametro di tipo con il tipo String, producendo un costruttore di tipo del tipo corretto da utilizzare come vincolato al contesto.

Esiste una proposta per consentire di esprimere direttamente tipi applicati parzialmente 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 

Potresti spiegare il significato del #From nella definizione di f2? Non sono sicuro di dove verrà costruito il tipo F (l'ho detto correttamente?)
Collin

1
Si chiama proiezione del tipo e fa riferimento a un membro Fromdel tipo To[String]. Non forniamo un argomento di tipo From, quindi ci riferiamo al costruttore del tipo, non a un tipo. Questo tipo di costruttore è del tipo giusto per essere usato come un contesto vincolato - * -> *. Ciò limita il parametro di tipo Trichiedendo un parametro implicito di tipo To[String]#From[T]. Espandi gli alias di tipo e voilà, ti rimane Function1[String, T].
retronym

dovrebbe essere Function1 [T, String]?
ssanj

18

Questa è un'altra nota tra parentesi.

Come ha sottolineato Ben , un vincolo di contesto rappresenta un vincolo "ha-un" tra un parametro di tipo e una classe di tipo. In altre parole, rappresenta un vincolo per l'esistenza di un valore implicito di una particolare classe di tipo.

Quando si utilizza un vincolo di contesto, spesso è necessario far emergere quel valore implicito. Ad esempio, dato il vincolo T : Ordering, spesso sarà necessaria l'istanza di Ordering[T]che soddisfi il vincolo. Come dimostrato qui , è possibile accedere al valore implicito utilizzando il implicitlymetodo o un metodo leggermente più utile context:

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) }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.