Wie ein abhängigen Typ in Scala emulieren
-
30-09-2019 - |
Frage
Ich versuche, einen generischen Restklassenring in Scala zu definieren. Ein Restklassenring wird durch einen Basisring (z die ganzen Zahlen) und ein Modul (beispielsweise zwei), definiert, der ein Wert aus dem Basisring ist. Beiden Ringe und ihre Elemente sind Objekte, also die Art des Moduls würde normalerweise ein abhängiges Typ sein, abhängig von dem Basisring. Ich verstehe dies in Scala nicht erlaubt (aus guten Gründen), so versuche ich, es zu emulieren, indem die Art annähert und eine Laufzeitprüfung zu tun, wenn der Restklassenring aufgebaut ist.
Die Definition von ResidueClassRing
ohne Fehler akzeptiert wird, jedoch nicht Scala nicht lassen Sie mich es instanziiert, für das Argument two
erhalte ich die Fehlermeldung
type mismatch;
found : dependenttypetest.DependentTypeTest.two.type
(with underlying type dependenttypetest.Integers.Integer)
required: dependenttypetest.EuclideanRing#E
Bin ich etwas falsch? Könnte dies ein Fehler in der Scala Typprüfer sein? Gibt es eine bessere Art und Weise ResidueClassRing
zu definieren?
Dies ist mit Scala 2.8.0 in dem Eclipse-IDE für Helios. Das Problem trat bereits für 2.7.x. Hier ist eine vereinfachte Version des Codes:
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);
}
Lösung
Das scheint zu funktionieren, aber ich konnte nicht von den Darstellern loszuwerden, wenn Vertreter der Berechnung:
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:. Seien Sie vorsichtig mit der Application-Eigenschaft, ist es zu Recht als veraltet
Andere Tipps
UPDATE: hinzugefügt IntRing
zu klären Änderungen in trait Ring
Das Problem scheint zu sein, dass der Typ Rückschließer automatisch die spezifische Art nicht wählen, welche ist das, was Sie in Ihrem Fall benötigen. Zusätzlich zu, dass Sie ein abhängiges Typargument in der gleichen Parameterliste als definiert Typen nicht haben können.
Was könnten Sie tun, ist die Instanz zu ziehen, dass der Typ abhängt, in dem äußeren Umfang (die in der Rings
Klasse gemacht wird) und die Compiler zu zwingen, den meisten spezifischen Typen zu wählen, wenn die Rings
Klasse instanziieren:
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)"
}
}
Jetzt können Sie es wie folgt verwendet werden:
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