两个数值表达式的总和

时间:2018-11-27 15:30:04

标签: scala

我正在写一些基于表情的玩具语言。这是一些获得想法的代码:

trait Expression[+T] {
  def eval: T
}

case class Literal[+T](value: T) extends Expression[T] {
  def eval = value
}

解析器构建一个表达式树,然后通过调用eval方法对其求值。现在,我想添加一个Sum表达式,该表达式表示其他两个表达式的总和:

case class Sum[+T: Numeric](left: Expression[T], right: Expression[T]) {
  def eval = implicitly[Numeric[T]].plus(left.eval, right.eval)
}

如果左右表达式具有相同的类型(由构造函数指定),则此方法很好。但是自然地,我也希望它能在以下情况下工作: Sum(Literal(1.1), Literal(1))

这不起作用,因为编译器找不到类型为Numeric[AnyVal]的隐式参数,这很有意义。

我想出了以下代码,使用类型限制来解决该问题:

case class Sum2[+T: Numeric, L <% T, R <% T](left: Expression[L], right: Expression[R]) extends Expression[T] {
  def eval = implicitly[Numeric[T]].plus(left.eval, right.eval)
}

现在,编译器抱怨left.evalright.eval的类型不是T。由于模糊的隐式参数,使用T强制转换为asInstanceOf[T]会产生更多的编译器错误。

实现此目标的正确方法是什么?

2 个答案:

答案 0 :(得分:0)

具体的问题是Sum(Literal(1.1), Literal(1))的左侧为Literal[Double],右侧为Literal[Int]。如您所见,IntDouble的LUB确实为AnyVal

https://scalafiddle.io/sf/ALM9urR/1

完全正常。我也认为这是个好习惯,因为添加不同的类型可能有点麻烦,但是您可以引入一个隐式变量,让您进行必要的转换。

答案 1 :(得分:0)

正如注释中指出的那样,对于您的操作而言,从IntDouble的安全转换这一事实不足以使编译器能够证明这种转换在所有情况下都是有效的。相关上下文。除了此代码(see also online)之外,我还没有其他任何一种更简单的方法来实现所需的目标:

trait Expression[+T] {
  def eval: T
}

trait TypeConverter[S, T] {
  def convert(value: S): T
}

trait TypeConverterLowPriority {
  implicit def compose[A, B, C](implicit aToB: TypeConverter[A, B], bToC: TypeConverter[B, C]): TypeConverter[A, C] = new TypeConverter.TypeConverterImpl(a => bToC.convert(aToB.convert(a)))
}

object TypeConverter extends TypeConverterLowPriority {

  class TypeConverterImpl[S, T](f: S => T) extends TypeConverter[S, T] {
    override def convert(value: S): T = f(value)
  }

  def sameType[T]: TypeConverter[T, T] = new TypeConverterImpl(identity)


  implicit val intToDouble: TypeConverter[Int, Double] = new TypeConverterImpl(_.toDouble)
  implicit val shortToInt: TypeConverter[Short, Int] = new TypeConverterImpl(_.toInt)
  // add more "primitive" type conversions here

}

case class Literal[+T](value: T) extends Expression[T] {
  def eval = value
}


trait BinaryOpImpl[A, B, R] {
  protected val numericR: Numeric[R]
  protected val aToR: TypeConverter[A, R]
  protected val bToR: TypeConverter[B, R]

  final def eval(left: A, right: B): R = evalImpl(aToR.convert(left), bToR.convert(right))

  protected def evalImpl(left: R, right: R): R
}


trait BinaryOpImplCompanionLowPriority[Ops[_, _, _]] {
  protected def build[A, B, R](numericR: Numeric[R], aToR: TypeConverter[A, R], bToR: TypeConverter[B, R]): Ops[A, B, R]

  implicit def castLeftToRight[L, R: Numeric](implicit tcl: TypeConverter[L, R]): Ops[L, R, R] = build(implicitly[Numeric[R]], tcl, TypeConverter.sameType)

  implicit def castRightToLeft[L: Numeric, R](implicit tcr: TypeConverter[R, L]): Ops[L, R, L] = build(implicitly[Numeric[L]], TypeConverter.sameType, tcr)

}

trait BinaryOpImplCompanion[Ops[_, _, _]] extends BinaryOpImplCompanionLowPriority[Ops] {
  implicit def sameType[T: Numeric]: Ops[T, T, T] = build(implicitly[Numeric[T]], TypeConverter.sameType, TypeConverter.sameType)
}

class SumImpl[A, B, R](val numericR: Numeric[R], val aToR: TypeConverter[A, R], val bToR: TypeConverter[B, R]) extends BinaryOpImpl[A, B, R] {
  override protected def evalImpl(left: R, right: R): R = numericR.plus(left, right)
}

object SumImpl extends BinaryOpImplCompanion[SumImpl] {
  override protected def build[A, B, R](numericR: Numeric[R], aToR: TypeConverter[A, R], bToR: TypeConverter[B, R]): SumImpl[A, B, R] = new SumImpl(numericR, aToR, bToR)
}

case class Sum[+T, L, R](left: Expression[L], right: Expression[R])(implicit impl: SumImpl[L, R, T]) extends Expression[T] {
  def eval = impl.eval(left.eval, right.eval)
}

用法示例:

def test(): Unit = {
  println(Sum(Literal(3), Literal(1)).eval)
  println(Sum(Literal(1.1), Literal(1)).eval)
  println(Sum(Literal(1), Literal(1.1)).eval)
  println(Sum(Literal[Short](1), Literal(1.12)).eval) // composite conversion Short -> Int -> Double
}

本质上,这个想法是拥有一个implicit变量来封装所有3种相关类型,而不是具有3个单独的隐式变量。因此,如果编译器可以为三元组LeftArgType-RightArgType-ResultType建立一个复合证据,则代码会遵守。