【问题标题】:DSL in scala to write chain comparisons such as a < b <= cscala中的DSL写链比较比如a < b <= c
【发布时间】:2013-06-19 20:54:24
【问题描述】:

我想在 Scala 中编写以下代码:

def method(a: Float, b: Float, c: Float) = {
  if( a < b <= c) {
    ...
  }
}

目前,这是无效的。实际上,a &lt; b 返回一个布尔值,为了比较,它被 booleanWrapper 包裹。然后编译器会抱怨 c 的类型是 Float 而不是 Boolean,因此它根本不会将 bc 进行比较。

是否有可能使用隐式类、方法和值类来实现这一点?

目前,我只能做到以下几点:

class Less(val previous: Boolean, last: Float) {
  def <=(other: Float): Less = new Less(previous && last <= other, other)
  def <(other: Float): Less = new Less(previous && last < other, other)
}
implicit def f(v: Float): Less = {
  new Less(true, v)
}
implicit def lessToBoolean(f: Less): Boolean = {
  f.previous
}

def method(a: Float, b: Float, c: Float) = {
  if( f(a) < b <= c) {
    ...
  }
}

有什么方法可以使用标准技巧来删除这个 f 吗?

【问题讨论】:

标签: scala comparison


【解决方案1】:

是的,您可以使用隐式和自定义运算符来模拟这一点。 这是一个简短的例子:

implicit class FloatWrapper(n:Float) {
  def :<(that:Float) = ResultAndLastItem(n<that, that)
  def :>(that:Float) = ResultAndLastItem(n>that, that)
  def :=(that:Float) = ResultAndLastItem(n==that, that)
  // insert more comparison operations here
}

case class ResultAndLastItem(result:Boolean, lastItem:Float) extends FloatWrapper(lastItem) {
  // insert boolean operations here
  // boolean operations should return another instance of ResultAndLastItem (?)
}

implicit def toBoolean(r:ResultAndLastItem):Boolean = r.result

这是 REPL 中的一个示例用法:

Welcome to Scala version 2.10.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_45).
Type in expressions to have them evaluated.
Type :help for more information.

scala> :paste
// Entering paste mode (ctrl-D to finish)

implicit class FloatWrapper(n:Float) {
  def :<(that:Float) = ResultAndLastItem(n<that, that)
  def :>(that:Float) = ResultAndLastItem(n>that, that)
  def :=(that:Float) = ResultAndLastItem(n==that, that)
  // insert more comparison operations here
}

case class ResultAndLastItem(result:Boolean, lastItem:Float) extends FloatWrapper(lastItem) {
  // insert boolean operations here
  // boolean operations should return another instance of ResultAndLastItem (?)
}

implicit def toBoolean(r:ResultAndLastItem):Boolean = r.result

// Exiting paste mode, now interpreting.

warning: there were 1 feature warnings; re-run with -feature for details
defined class FloatWrapper
defined class ResultAndLastItem
toBoolean: (r: ResultAndLastItem)Boolean

scala> if(2 :< 3 :< 4) "yay" else "nope"
res0: String = yay

scala> if(2 :< 3 :< 3) "nope" else "yay"
res1: String = yay

备注:

  • 您可以轻松添加更多比较运算符,例如:&lt;=

  • 需要使用自定义运算符,因为这会强制编译器使用上面的implicit class,而不是Floats 的内置默认运算符。

  • 很难让我的示例更通用,以便允许所有可比较的类型,例如 DoubleInt 或自定义类型。这很难的原因是因为 implicit class FloatWrapper 不能要求更多的 implicit 参数 - 不能嵌套隐式。 (或者更准确地说,它们可以在语法上,但编译器不会选择它们进行隐式解析。)

  • 作为一种优化,您可能需要考虑添加布尔快捷方式,即当已知表达式为 false 时,无需评估剩余的比较。

    • 当您还添加布尔运算符(例如 ||&amp;&amp;)时,这会变得很棘手。

【讨论】:

    【解决方案2】:

    如果您使用具有方法&lt;&lt;= 的标准类型,则如果没有显式包装,这是不可能的。但是,如果您提供自己的类型,则可以这样做。

    def method(a:SpecialFloat, b:SpecialFloat, c:SpecialFloat)
    
    implicit class SpecialFloat(v:Float) extends AnyVal {
    
      def < (other:Float) = ??? 
      def <= (other:Float) = ???
    }
    

    这样你就可以用你喜欢的任何方式来实现这些方法。调用 method(1f, 2f, 3f) 会自动将它们转换为 SpecialFloat 实例 (more information about the extends AnyVal)。

    【讨论】:

      猜你喜欢
      • 2021-10-04
      • 1970-01-01
      • 1970-01-01
      • 2015-12-28
      • 2022-10-24
      • 1970-01-01
      • 2012-09-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多