Comment émuler un type dépendant de Scala
-
30-09-2019 - |
Question
Je suis en train de définir un anneau de classe générique reste dans Scala. Un anneau de classe résidu est définie par une bague de base (par exemple, les nombres entiers de) et un module (par exemple deux), qui est une valeur à partir de l'anneau de base. Les deux bagues et les éléments sont des objets, par conséquent, le type du module serait normalement un type dépendant, en fonction de l'anneau de base. Je comprends que ce n'est pas autorisé à Scala (pour de bonnes raisons), donc je suis en train de l'imiter en se rapprochant du type et de faire un contrôle d'exécution lorsque l'anneau de classe résidu est construit.
La définition de ResidueClassRing
est acceptée sans erreur, cependant, Scala ne laisse pas me instancier, pour le two
argument que je reçois le message d'erreur
type mismatch;
found : dependenttypetest.DependentTypeTest.two.type
(with underlying type dependenttypetest.Integers.Integer)
required: dependenttypetest.EuclideanRing#E
Ai-je fait quelque chose de mal? Serait-ce un bug dans le type Scala vérificateur? Y at-il une meilleure façon de définir ResidueClassRing
?
est avec Scala 2.8.0 dans l'IDE Eclipse Helios. Le problème déjà eu lieu pour 2.7.x. Voici une version simplifiée du code:
package dependenttypetest
class EuclideanRing
{
thisRing =>
type E <: EuclideanRingElement;
def one: E;
trait EuclideanRingElement
{
def ring = thisRing;
def +(b: E): E;
def %(b: E): E;
}
}
object Integers extends EuclideanRing
{
type E = Integer;
val one: Integer = new Integer(1);
class Integer(n: Int) extends EuclideanRingElement
{
val intValue: Int = n;
def +(b: Integer): Integer = new Integer(intValue + b.intValue);
def %(b: Integer): Integer = new Integer(intValue % b.intValue);
}
}
class ResidueClassRing (val baseRing : EuclideanRing, m : EuclideanRing#E)
{
val modulus: baseRing.E =
m match {
case e: baseRing.E if m.ring == baseRing => e;
case _ => throw new IllegalArgumentException("modulus not from base ring");
};
type E = ResidueClassRingElement;
def one: E = new ResidueClassRingElement(baseRing.one);
class ResidueClassRingElement (e : baseRing.E)
{
def representative: baseRing.E = e % modulus;
def +(b: E) = new ResidueClassRingElement(
this.representative + b.representative);
}
}
object DependentTypeTest extends Application
{
val two = new Integers.Integer(2);
val mod2ring = new ResidueClassRing(Integers, two);
println(mod2ring.one + mod2ring.one);
}
La solution
Cela semble fonctionner, mais je ne pouvais pas se débarrasser de la distribution lors du calcul représentant:
package dependenttypetest
abstract class EuclideanRing{
thisRing =>
type E <: EuclideanRingElement;
def one: E;
trait EuclideanRingElement
{
def ring = thisRing;
def +(b: E): E;
def %(b: E): E;
}
}
class Integers extends EuclideanRing {
type E = Integer;
val one: Integer = new Integer(1);
class Integer(n: Int) extends EuclideanRingElement
{
val intValue: Int = n;
def +(b: Integer): Integer = new Integer(intValue + b.intValue);
def %(b: Integer): Integer = new Integer(intValue % b.intValue);
override def toString = "Int" + intValue
}
}
object Integers extends Integers
class ResidueClassRing[ER <: EuclideanRing] (modulus : ER#E) {
val baseRing = modulus.ring
type E = ResidueClassRingElement;
def one: E = new ResidueClassRingElement(baseRing.one);
class ResidueClassRingElement (e : baseRing.E)
{
def representative = e % modulus.asInstanceOf[baseRing.E];
def +(b: E) = new ResidueClassRingElement(
this.representative + b.representative);
override def toString = "RC(" + representative + ")"
}
}
object DependentTypeTest extends Application {
val two = new Integers.Integer(2);
val mod2ring = new ResidueClassRing[Integers](two)
println(mod2ring.one + mod2ring.one)
}
BTW:. Soyez prudent avec le trait d'application, il est à juste titre dépréciée
Autres conseils
Mise à jour: Ajout IntRing
pour clarifier les changements dans trait Ring
Le problème semble être que le type inferencer ne capte pas automatiquement le type le plus spécifique qui est ce que vous avez besoin dans votre cas. En plus de cela, vous ne pouvez pas avoir un argument de type dépendant de la même liste de paramètres comme le type de définition.
Qu'est-ce que vous pourriez faire est de tirer l'instance que le type dépend du périmètre extérieur (ce qui se fait dans la classe Rings
) et pour forcer le compilateur de choisir le type le plus spécifique lors de l'instanciation de la classe Rings
:
trait Ring {
type Element <: EuclideanRingElement
def one: Element
// for convenience could be defined anywhere of course
lazy val rings: Rings[this.type] = new Rings[this.type](this)
trait EuclideanRingElement {
def +(e: Element): Element
def %(e: Element): Element
}
}
class Rings[R <: Ring](val base: R) {
class ResidueClassRing(m: base.Element) {
def one = new Element(base.one)
class Element(e: base.Element) {
def repr = e % m
def +(that: Element) = new Element(this.repr + that.repr)
}
}
}
object IntRing extends Ring {
val one = new Element(1)
class Element(val n: Int) extends EuclideanRingElement {
def +(that: Element) = new Element(this.n + that.n)
def %(that: Element) = new Element(this.n % that.n)
override def toString = n formatted "Int(%d)"
}
}
Maintenant, vous pouvez l'utiliser comme ceci:
scala> import IntRing._
import IntRing._
scala> val two = new Element(2)
two: IntRing.Element = Int(2)
scala> val r2 = new rings.ResidueClassRing(two)
r2: IntRing.rings.ResidueClassRing = Rings$ResidueClassRing@4b5075f9