Проектирование системы нумерации с неподписанными и подписанными стандартами

Я пытаюсь создать систему нумерации как для целых чисел без знака, так и для целых чисел. Оба этих типа имеют значение underlying, которое представляет число в системе с номером Scala. Вот иерархия типов, которую я имею до сих пор.

sealed trait Number {
  def + (num : Number) : Number = ???
  def - (num : Number) : Number = ???
  def * (num : Number) : Number = ???
}

sealed trait SignedNumber extends Number

sealed trait UnsignedNumber extends Number

sealed trait UInt32 extends UnsignedNumber {
  def underlying : Long
}

sealed trait UInt64 extends UnsignedNumber {
  def underlying : BigInt
}

sealed trait Int32 extends SignedNumber {
  def underlying : Int
}

sealed trait Int64 extends SignedNumber {
  def underlying : Long
}

Я хотел бы определить underlying в признаке Number, чтобы компилятор мог обеспечить, чтобы underlying определялся во всех дочерних элементах. Однако типы для underlying изменяются для каждого признака - я хочу сохранить наименьший возможный тип для каждого типа. Например, UInt32 можно сохранить как long в Scala, а UInt64 необходимо сохранить как BigInt.

Каков наиболее эффективный способ сделать это?

Ответ 1

Вы можете объявить type в родительском признаке и переопределить его в подзаголовках.

sealed trait Number {
  type A
  def underlying: A
  def + (num : Number) : Number = ???
  def - (num : Number) : Number = ???
  def * (num : Number) : Number = ???
}

sealed trait SignedNumber extends Number

sealed trait UnsignedNumber extends Number

sealed trait UInt32 extends UnsignedNumber {
  override type A = Long
}

sealed trait UInt64 extends UnsignedNumber {
  override type A = BigInt
}

sealed trait Int32 extends SignedNumber {
  override type A = Int
}

sealed trait Int64 extends SignedNumber {
  override type A = Long
}

Пример, чтобы показать использование зависимого от пути типа в случае, если это не ясно:

def getUnderlying(x: Number): x.A = x.underlying

Чтобы получить правильные типы возвращаемых данных, я думаю, потребуется другое type.

sealed trait Number {
  type A
  type B
  def underlying: A
  def +(that: B): B
}

sealed trait UInt32 extends Number { x =>
  override type A = Long
  override type B = UInt32
  override def +(y: B): B = new UInt32 {
    // todo - naive implementation, doesn't check overflow
    override val underlying = x.underlying + y.underlying
  }
}

def main(args: Array[String]) {
  print((
    new UInt32 { def underlying = 3 } +
    new UInt32 { def underlying = 4 }
  ).underlying)
}

Ответ 2

Наиболее эффективным способом является сохранение примитивных чисел (Int Double...) в качестве исходного типа.

Незнакомость должна храниться в параметре Type, который будет удален во время выполнения. Scala делает это, когда вы позволяете простым классам классов расширять AnyVal.

Следующий код делает это для Ints, Longs, Doubles и Bigint. Я добавил несколько классификаций при использовании unsigned и переименовал unsigned в positive.

Кроме того, поскольку классификация выполняется в системе типов, нам не нужно предоставлять столько перегруженных функций + - и *. Это позволит сэкономить место при попытке реализовать это для всех типов номеров.

До сих пор мало что нужно сделать, когда соединяются между различными типами. Я посмотрю на это позже.

Характеристики классификации:

sealed trait SignTag{
  type SubTag <:SignTag;
  type AddTag <:SignTag;
  type MultTag<:SignTag;
}

sealed trait Signed extends SignTag{
  type SubTag=Signed;
  type AddTag=Signed;
  type MultTag=Signed;
}

sealed trait Positive extends SignTag{
  type SubTag=Signed;
  type AddTag=Negative;
  type MultTag=Negative;
}

sealed trait Negative extends SignTag{
  type SubTag=Signed;
  type AddTag=Negative;
  type MultTag=Positive;
}

sealed trait Zero extends SignTag{
  type SubTag=Zero;
  type AddTag=Zero;
  type MultTag=Zero;
}

Int wrapper:

object SInt {
  @inline
  implicit def toSigned[T <: SignTag](int:SInt[T]):SInt[Signed]=int.asInstanceOf[SInt[Signed]];

  @inline implicit def toLong[T <: SignTag](int:SInt[T]):SLong[T]=SLong(int.underlying);
  @inline implicit def toDouble[T <: SignTag](int:SInt[T]):SDouble[T]=SDouble(int.underlying);
  @inline implicit def toBig[T <: SignTag](int:SInt[T]):SBigInt[T]=SBigInt(int.underlying);
}

case class SInt[T <: SignTag](val underlying:Int) extends AnyVal{
  def -(second: SInt[_ <: T#InTag]):SInt[T#SubTag]=new SInt[T#SubTag](underlying - second.underlying);

  def +(second: SInt[_ <: T#InTag]):SInt[T#AddTag]=new SInt[T#AddTag](underlying + second.underlying);

  def *(second: SInt[_ <: T#InTag]):SInt[T#MultTag]=new SInt[T#MultTag](underlying * second.underlying);

  def assertSameType(other:SInt[T])={};
}

Длинная обертка:

object SLong {

  @inline
  implicit def toSigned[T <: SignTag](int:SLong[T]):SLong[Signed]=int.asInstanceOf[SLong[Signed]];

  @inline implicit def toDouble[T <: SignTag](int:SLong[T]):SDouble[T]=SDouble(int.underlying);
  @inline implicit def toBig[T <: SignTag](int:SLong[T]):SBigInt[T]=SBigInt(int.underlying);
}

case class SLong[T <: SignTag](val underlying:Long) extends AnyVal{
  def -(second: SLong[_ <: T#InTag]):SLong[T#SubTag]=new SLong[T#SubTag](underlying - second.underlying);

  def +(second: SLong[_ <: T#InTag]):SLong[T#AddTag]=new SLong[T#AddTag](underlying + second.underlying);

  def *(second: SLong[_ <: T#InTag]):SLong[T#MultTag]=new SLong[T#MultTag](underlying * second.underlying);

  def assertSameType(other:SLong[T])={};
}

Двойная обертка:

object SDouble {
  @inline
  implicit def toSigned[T <: SignTag](int:SDouble[T]):SDouble[Signed]=int.asInstanceOf[SDouble[Signed]];
}

case class SDouble[T <: SignTag](val underlying:Double) extends AnyVal{
  def -(second: SDouble[_ <: T#InTag]):SDouble[T#SubTag]=new SDouble[T#SubTag](underlying - second.underlying);

  def +(second: SDouble[_ <: T#InTag]):SDouble[T#AddTag]=new SDouble[T#AddTag](underlying + second.underlying);

  def *(second: SDouble[_ <: T#InTag]):SDouble[T#MultTag]=new SDouble[T#MultTag](underlying * second.underlying);

  def assertSameType(other:SDouble[T])={};
}

Обертка BigInt:

object SBigInt {
  @inline
  implicit def toSigned[T <: SignTag](int:SLong[T]):SLong[Signed]=int.asInstanceOf[SLong[Signed]];

  @inline
  implicit def toDouble[T <: SignTag](int:SBigInt[T]):SDouble[T]=SDouble(int.underlying.toDouble);
}

case class SBigInt[T <: SignTag](val underlying:BigInt) extends AnyVal{
  def -(second: SBigInt[_ <: T#InTag]):SBigInt[T#SubTag]=new SBigInt[T#SubTag](underlying - second.underlying);

  def +(second: SBigInt[_ <: T#InTag]):SBigInt[T#AddTag]=new SBigInt[T#AddTag](underlying + second.underlying);

  def *(second: SBigInt[_ <: T#InTag]):SBigInt[T#MultTag]=new SBigInt[T#MultTag](underlying * second.underlying);

  def assertSameType(other:SBigInt[T])={};
}

Проверьте синтаксис:

class CompileToTest {
  val signed=new SInt[Signed](5);
  val positive=new SInt[Positive](5);
  val negative=new SInt[Negative](-5);
  val zero=new SInt[Zero](0);

  (signed + signed).assertSameType(signed);
  (negative + signed).assertSameType(signed);
  (positive - positive).assertSameType(signed);
  (positive * negative).assertSameType(signed);
  (zero + zero).assertSameType(zero);

  val positiveDouble=SDouble[Positive](4.4)
  val negativeDouble=SDouble[Negative](-4.4)
  val signedDouble=SDouble[Signed](-4.4)

  (positiveDouble * negativeDouble).assertSameType(signedDouble);
}

Ps. На самом деле не смотрели на байт-код, но docs заявляет, что это должно быть встроено и скомпилировано до примитивов.

Мне просто нравится этот lanuage.