您基本上可以使用 scala 宏来实现这一点,尽管您不会为 Ordering 这样做。相反,您将拥有一个适用于任何实现< 或> 函数形式的类的函数。它会在编译时做尽可能多的工作,如果没有找到可接受的小于或大于的实现,它会在编译时失败。
首先你需要定义一个宏对象,来包含实现:
// macros.scala
import scala.reflect.runtime.universe._
import scala.reflect.macros.blackbox.Context
object Macros {
def clamp[A](c: Context)(low: c.Expr[A], high: c.Expr[A])(value: c.Expr[A]): c.Expr[A] = {
import c.universe._
val tree =
q"""
val lowResult = $low
val valueResult = $value
var hasValue = false
var result = valueResult
if (valueResult < lowResult) {
hasValue = true
result = lowResult
}
if (!hasValue) {
val highResult = $high
if (valueResult > highResult) {
result = highResult
}
}
result
"""
c.Expr(tree)
}
}
注意:根据 Karol S 的建议,我重写了宏以尽可能少地评估其参数。如果low、high 或value 的输入很昂贵,它只会评估low 和value 一次。 high 仅在未发现值为 low 时才被评估。这依赖于一些 var,但可变状态完全包含在宏的主体中,调用时可以安全地忽略。
一旦编写完成,宏就可以被外部代码中的普通函数引用:
// main.scala
import scala.language.experimental.macros
object Main {
def clamp[A](low: A, high: A)(value: A): A = macro Macros.clamp[A]
def main(args: Array[String]): Unit = {
val int = clamp(0, 10)(20)
}
}
使用 -print 编译时会生成以下代码:
package <empty> {
object Main extends Object {
def main(args: Array[String]): Unit = {
val int: Int = ({
val lowResult: Int = 0;
val valueResult: Int = 20;
var hasValue: Boolean = false;
var result: Int = valueResult;
if (valueResult.<(lowResult))
{
hasValue = true;
result = lowResult
}
else
();
if (hasValue.unary_!())
{
val highResult: Int = 10;
if (valueResult.>(highResult))
result = highResult
else
()
}
else
();
result
}: Int);
()
};
def <init>(): Main.type = {
Main.super.<init>();
()
}
}
}
这避免了装箱,尽管(如@specialized)它会稍微增加编译代码的大小,方法是将 if 语句直接插入到任何使用它的代码中。它还需要创建一些临时变量并进行布尔检查以防止对输入进行多次评估,但这些影响应该非常低。它适用于文字和运行时值,只要它知道类型(它几乎总是可以推断)。