Come emulare un tipo dipendente a Scala
-
30-09-2019 - |
Domanda
Sto cercando di definire una generica anello di classe residuo a Scala. Un anello classe residuo è definita da un anello di base (per esempio i numeri interi) ed un modulo (ad esempio due), che è un valore compreso tra l'anello di base. Entrambi gli anelli ei relativi elementi sono oggetti, quindi al tipo di modulo sarebbe normalmente un tipo dipendente, a seconda l'anello di base. Capisco che questo non è consentito a Scala (per buone ragioni), quindi sto cercando di emularlo approssimando il tipo e facendo un controllo runtime quando l'anello di classe residuo viene costruito.
La definizione di ResidueClassRing
è accettata senza errori, però, Scala non mi permette di un'istanza, per il two
argomento che il messaggio di errore
type mismatch;
found : dependenttypetest.DependentTypeTest.two.type
(with underlying type dependenttypetest.Integers.Integer)
required: dependenttypetest.EuclideanRing#E
sto facendo qualcosa di sbagliato? Questo potrebbe essere un bug nel tipo di controllo Scala? C'è un modo migliore per definire ResidueClassRing
?
Questo è con Scala 2.8.0 in Eclipse IDE per Helios. Il problema già verificato per 2.7.x. Ecco una versione semplificata del codice:
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);
}
Soluzione
Questo sembra funzionare, ma non riusciva a liberarsi del cast per il calcolo rappresentante:
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)
}
A proposito:. Fare attenzione con il tratto di applicazione, è giustamente deprecato
Altri suggerimenti
UPDATE: IntRing
aggiunta per chiarire cambiamenti trait Ring
Il problema sembra essere che il tipo inferencer non seleziona automaticamente il tipo specifico più che è quello che serve nel vostro caso. In aggiunta a ciò, non si può avere un tipo di argomento dipendente nella stessa lista di parametri come il tipo di definizione.
Che cosa si potrebbe fare è tirare l'istanza che il tipo dipende nel perimetro esterno (che è fatto nella classe Rings
) e per forzare il compilatore di scegliere il tipo più specifico quando un'istanza della 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)"
}
}
Ora è possibile utilizzare in questo modo:
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