将参数限制为Scala中定义的类型

时间:2019-01-07 01:57:51

标签: scala types

我希望能够将对象包装在Scala的容器中,以支持我正在开发的AST类型语言。即,所有容器都将具有相等性,而某些容器将具有更扩展的布尔比较器(>=><<=)。 AST将支持以下类型:

IntBigDecimalStringLocalDateSome(Int)Some(BigDecimal)Some(String)Some(LocalDate)和以上任何单数类型的Set,即Set[Int],但不是Set[AnyVal]

我已经设置好了

sealed trait Containable

// This trait will contain those types that can be subject to 
// boolean comparison. Such as Date, BigDecimal, Int
// How can I restrict the value to these types?
sealed trait ContainableComparable extends Containable

// How can I restrict this to only Int, BigDecimal types?
// This should probably contain some sort of val numeric: ???
// But because BigDecimal is from scala.math and is not a true primitive 
// (doesn't inherit from AnyVal), not sure how to limit this to a 
// numeric value
sealed trait ContainableNumeric extends ContainableComparable

final case class ContainableInt(int: Int) extends ContainableNumeric
final case class ContainableBigDecimal(bd: BigDecimal) extends ContainableNumeric
final case class ContainableString(str: String) extends Containable
final case class ContainableBoolean(bool: Boolean) extends Containable
final case class ContainableDate(date: LocalDate) extends Containable

sealed trait Container {
  val value: Containable
}

case class ContainerBoolean(value: ContainableBoolean) extends Container
case class ContainerNumeric(value: ContainableNumeric) extends Container
case class ContainerDate(value: ContainableDate) extends Container
case class ContainerString(value: ContainableString) extends Container

sealed trait ContainerSet {
  val values: Set[Containable]
}

object ContainerSet {
  def apply[T <: Containable](set: Set[T]): ContainerSet[Containable] = {
      set apply {
        case s: Set[ContainableInt] => ContainerSet[ContainableInt](s.map(ContainerNumeric.apply))
        case s: Set[ContainableBigDecimal] => ContainerSet[ContainableBigDecimal](s.map(ContainerNumeric.apply))
        case s: Set[ContainableString] => ContainerSet[ContainableString](s.map(ContainerString.apply))
        case s: Set[ContainableDate] => ContainerSet[ContainbleDate](s.map(ContainerDate.apply))
      }
  }
}

在这里,我不认为塑形是正确的工具。根据我的理解,“元”是无形的。换句话说,无形状允许我们将作为对象的类型参数限制为对象的特定子集。在这里,我试图将原语的类型参数限制为原语的某个子集。

1 个答案:

答案 0 :(得分:0)

我不确定我是否理解您的问题,尤其是目前尚不清楚应如何使用ContainableNumeric。我仍然认为implicit参数可能是解决问题的方法。您想要做的就是为您允许的类创建一个密封的typeclass,并要求该泛型类型受该类型类的约束。这是它的外观简化示例:

@implicitNotFound("""Cannot find implicit value for Containable[${T}]. The type ${T} is not Containable.""")
sealed trait Containable[T] {
  def wrap(value: T): Container[T]
}

sealed trait ContainableComparable[T] extends Containable[T] with Ordering[T]

sealed trait ContainableNumeric[T] extends ContainableComparable[T]

object Containable {

  private class ContainableImpl[T] extends Containable[T] {
    override def wrap(value: T): Container[T] = new Container[T](value)(this)
  }

  private class ContainableNumericImpl[T: Numeric] extends ContainableImpl[T] with ContainableNumeric[T] {
    override def compare(x: T, y: T): Int = implicitly[Numeric[T]].compare(x, y)
  }

  private class ContainableComparableImpl[T <: Comparable[T]] extends ContainableImpl[T] with ContainableComparable[T] {

    override def compare(x: T, y: T): Int = x.compareTo(y)
  }


  implicit val containableInt: ContainableNumeric[Int] = new ContainableNumericImpl[Int]
  implicit val containableBd: ContainableNumeric[BigDecimal] = new ContainableNumericImpl[BigDecimal]

  implicit val containableString: ContainableComparable[String] = new ContainableComparableImpl[String]
  implicit val containableBool: Containable[Boolean] = new ContainableImpl[Boolean]
}


sealed case class Container[T: Containable](value: T)


sealed case class ContainerSet[T: Containable](value: Set[T])

sealed class ContainerSet2[T: Containable](value: Set[Container[T]])

object ContainerSet2 {
  def apply[T: Containable](set: Set[T]): ContainerSet2[T] = {
    new ContainerSet2(set.map(el => implicitly[Containable[T]].wrap(el)))
  }
}

这是一个用法示例:

def test(): Unit = {
  val ci = Container(1)
  val cs = Container("abc")
  // val cd = Container(1.0)// fails with a compilation error

  val csi = ContainerSet(Set(1))
  val cs2i = ContainerSet2(Set(1))
  // val csd = ContainerSet(Set(1.0))// fails with a compilation error
  // val cs2d = ContainerSet2(Set(1.0))// fails with a compilation error
}

由于您没有使用ContainerSet的示例,因此我提供了两种不同的可能实现:一种将整个Set包装起来,而另一种将每个元素包装在集合内,就像您的原始代码一样。

此代码可能不是您真正想要的,但是如果没有用法示例,很难猜测,这可能是朝着正确方向迈出的一步。