40 votes

Que fait T {} à Scala?

La navigation Informe le code, je suis tombé sur ce qui semble superflu {} ici et ici:

trait Witness extends Serializable {
  type T
  val value: T {}
}

trait SingletonOps {
  import record._
  type T
  def narrow: T {} = witness.value
}

J'ai presque ignoré comme une faute de frappe, car il ne fait rien, mais apparemment, il fait quelque chose. Voir à ce commit: https://github.com/milessabin/shapeless/commit/56a3de48094e691d56a937ccf461d808de391961

Je n'ai aucune idée de ce qu'il fait. Quelqu'un peut m'expliquer?

54voto

Miles Sabin Points 13604

N'importe quel type peut être suivie par un {} clos de la séquence du type et de l'abstrait non-membre de type définitions. Ceci est connu comme un "raffinement" et est utilisé pour fournir plus de précision sur le type de base est en train d'être affinés. Dans la pratique, les améliorations sont les plus couramment utilisés pour exprimer des contraintes sur le type abstrait des membres de la type raffinés.

C'est un fait peu connu que cette séquence peut être vide, et dans la forme que vous pouvez voir dans la forme de code source, T {} est le type T avec un vide de raffinement. Vide le raffinement est ... vide ... donc ne pas ajouter des contraintes supplémentaires à l'raffiné type et par conséquent les types T et T {} sont équivalentes. Nous pouvons obtenir la Scala compilateur de vérifier que, pour nous, comme si,

scala> implicitly[Int =:= Int {}]
res0: =:=[Int,Int] = <function1>

Alors, pourquoi ferais-je une telle apparemment inutiles chose en informe? C'est en raison de l'interaction entre la présence de raffinements et de l'inférence de type. Si vous regardez dans la section pertinente de la Scala de Spécification de Langage, vous verrez que l'algorithme d'inférence de types tente d'éviter d'inférer singleton types, au moins dans certaines circonstances. Voici un exemple de ce que fait,

scala> class Foo ; val foo = new Foo
defined class Foo
foo: Foo = Foo@8bd1b6a

scala> val f1 = foo
f1: Foo = Foo@8bd1b6a

scala> val f2: foo.type = foo
f2: foo.type = Foo@8bd1b6a

Comme vous pouvez le voir à partir de la définition de l' f2 de la Scala compilateur sait que la valeur foo a la plus précise du type foo.type (ie. le singleton type d' val foo), cependant, sauf s'il est explicitement demandé de ne pas en déduire que plus précise du type. Au lieu de cela, il déduit la non-singleton (c'est à dire. élargi) type Foo comme vous pouvez le voir dans le cas de l' f1.

Mais dans le cas d' Witness en informe, j'ai explicitement voulez le singleton de type déduire pour l'utilisation de l' value membre (le point de l'ensemble de l' Witness est de nous permettre de passer entre le type et la valeur des niveaux via singleton types), donc est-il possible que le compilateur Scala peuvent être incités à le faire?

Il s'avère qu'un vide de raffinement est exactement ce que fait,

scala> def narrow[T <: AnyRef](t: T): t.type = t
narrow: [T <: AnyRef](t: T)t.type

scala> val s1 = narrow("foo")  // Widened
s1: String = foo

scala> def narrow[T <: AnyRef](t: T): t.type {} = t  // Note empty refinement
narrow: [T <: AnyRef](t: T)t.type

scala> val s2 = narrow("foo")  // Not widened
s2: String("foo") = foo

Comme vous pouvez le voir ci-dessus REPL transcription, dans le premier cas s1 a été tapé comme l'élargissement de type String alors qu' s2 a été attribuée le singleton de type String("foo").

Est-ce mandaté par le SLS? Non, mais il est cohérent avec lui, et il fait une sorte de sens. Beaucoup de Scala est l'inférence de type mécanique de mise en œuvre définies plutôt que de spec ed et c'est probablement l'une des moins surprenant et problématique les instances de cette.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X