在已知支持类型之前指定算术运算?

问题描述 投票:0回答:1

我正在尝试编写一个接口,用户应该能够通过该接口传递独立于数字类型的任意算术函数,然后帮助程序代码将它们绑定到系统使用的适当支持

Numeric
。 (对此的需求很复杂,但这是一个 DSL 设计问题,最终使用的数字类型在编译时是未知的,尽管它可以限制在一个小集合中。)

所以目标是得到类似的东西:

case class numericOp(
  intVersion : (Int, Int) -> Int,
  floatVersion : (Float, Float) -> Float
)
def wrap(f : ???) : NumericOp = NumericOp(f[Int], f[Float])

//use site
wrap( (x, y) => x + y)

//use in engine
def applyArithmetic(arg1 : Any, arg2 : Any, op : NumericOp) : Any{
  (arg1, arg2) match {
    case (a : Int, b : Int) => op.intVersion(a, b)
    case (a : Float, b : Float) => op.floatVersion(a, b)
    case _ => throw new Exception("Invalid types") //shouldn't actually be reachable

}

上面的代码 - 显然无法编译 - 会

wrap
采用带有未应用类型参数的泛型 lambda,然后将支持类型应用于它以创建真正的函数。

我理解为什么这不起作用 - 类型参数应用程序必须在运行时之前发生,即在 lambda 甚至存在之前。也就是说,我正在尝试做的核心 - 将算术逻辑描述为数据,然后在类型可用后创建特定的实现 - 似乎应该是可能的。 (我的理由是,我可以描述

Numeric
下所有操作的完整 DSL,让用户在该 DSL 中指定他们的数学,然后稍后为
Int
/
Float
/
Whatever
创建实现。)

有办法让这个工作吗?

我非常希望保持使用站点简洁,更重要的是,从支持类型中抽象出来。

给出一些更明确的细节和要求:

  1. 首先,用户以某种与类型无关的方式指定算术运算。他们知道自己正在处理某种
    Numeric
    类型很好,但他们不必知道特定类型,甚至可能类型的数量。我不知道这个输入可以采用什么格式,因此 ???在代码中。
  2. 他们的规范 - 称之为
    f
    - 被传递给处理函数(
    wrap
    ,上面)。该函数可以知道可能的支持类型集,并生成一个中间结构来存储任何所需的特定于类型的数据(
    numericOp
    ,上面)
  3. 中间结构在对动态
    Any
    值进行操作的解释器中使用(对输入 DSL 代码进行类型检查,并在需要时进行转换)。这是在上面的
    applyArithmetic
    中完成的。

要从更暂时的角度来看待它:

  1. 用户指定一些算术逻辑,将其传递给
    wrap
    函数
  2. 系统维护人员重新排列可能的支持数字类型集,在不通知用户的情况下添加/删除它们
  3. 代码已编译完成
  4. 在运行时,特定值作为
    applyArithmetic
    以及任何中间类型
    Any
    输出传递给
    wrap
    函数。
scala lambda type-parameter
1个回答
4
投票

如果您使用类似

Numeric[Int]
之类的东西,编译器会在编译时将其解析为某个值,然后使用它。因此,正如您所怀疑的那样,它将被硬编码。

def func(arg1: Int, arg2: Int): Int = {
  Numeric[Int].plus(arg1, arg2)
}

如果你这样做的话,情况就不一样了:

def func[T: Numeric](arg1: T, arg2: T): T = {
  Numeric[T].plus(arg1, arg2)
}

为什么?

def func[T: Numeric](arg1: T, arg2: T): T = ...

的语法糖
def func[T](arg1: T, arg2: T)(implicit generatedName: Numeric[T]): T = ...

因此

def numericOp[T: Numeric](arg1: T, arg2: T): T = {
  Numeric[T].plus(arg1, arg2)
}

numericOp(1, 2)
numericOp(1L, 2L)
numericOp(1.0, 2.0)

的语法糖
def numericOp[T](arg1: T, arg2: T)(implicit genName: Numeric[T]): T = {
  genName.plus(arg1, arg2)
}

// Integral is subtype of Numeric and implicit finds
// these implementations in Numeric companion object
numericOp(1, 2)(Numeric.IntIsIntegral)
numericOp(1L, 2L)(Numeric.LongIsIntegral)
numericOp(1.0, 2.0)(Numeric.DoubleIsIntegral)

如果您始终在类型类中使用隐式 - 并且通常还使用扩展方法以使其更具可读性:

// Scala 2
implicit class NumericOps[T](private val value: T) extends AnyVal {
  
  def +(another: T)(implicit T: Numeric[T]): T = T.plus(value, another)
}

// Scala 3
extension [T](value: T)
  def +(another: T)(implicit T: Numeric[T]): T = T.plus(value, another)

这个参数化接口的实现已针对类型进行了解析 - 推迟了

T
的规范(并解析为已知类型的隐式值),这是一个称为无标记最终的示例。

替代方法是使用自由代数:

sealed trait NumericExpr[T] {

  import NumericExpr._

  def + (another: NumericExpr[T]): NumericExpr[T] =
    Plus(this, another)
  def - (another: NumericExpr[T]): NumericExpr[T] =
    Minus(this, another)

  def run(plus: (T, T) => T)(minus: (T, T) => T): T = this match {
    case Value(t) => t
    case Plus(a, b) => plus(run(a), run(b))
    case Minus(a, b) => minus(run(a), run(b))
  }
}
object NumericExpr {
  case class Value[T](number: T) extends NumericExpr
  case class Plus[T](a: NumericExpr[T], b: a: NumericExpr[T]) extends NumericExpr
  case class Minus[T](a: NumericExpr[T], b: a: NumericExpr[T]) extends NumericExpr

  def wrap[T](t: T): NumericExpr[T] = Value(t)
}

您使用它的方式是将所有值包装在此包装器类型中,并让此包装器类型“记录”操作(不知道确切的实现),稍后您将“重放”提供实现作为最后一步。

// generic code works with free algebra
def numericOp[T](arg1: NumericExpr[T], arg2: NumericExpr[T]): NumericExpr[T] = {
  arg1 + arg2
}

// specific code lifts values into free algebra and later provide implementation
numericOp(NumericExpr.wrap(1), NumericExpr.warp(2)).run(_ + _)(_ - _)

编辑:

如果您想要无标签的 Final 式方法,有 2 个选项:

Scala 2:

// There are no polymorphic methods in Scala 2
// and neither there are funtions with implicits
// - we you have to attach such method to an interface
trait numericOps {
  def apply[T: Numeric](t1: T, t2: T): T
}

val plus: numericOps = new numericOps {
  def apply[T: Numeric](t1: T, t2: T): T = Numeric[T].plus(t1, t2)
}

plus2(1, 2)

Scala 3:

// Has both polymorphic function types as well
// as context functions
type numericOps = [T] => (T, T) => Numeric[T] ?=> T

val plus: numericOps = [T] => (t1: T, t2: T) => (num: Numeric[T]) ?=> num.plus(t1, t2)

plus(1, 2)

参见 演示

© www.soinside.com 2019 - 2024. All rights reserved.