我想实现一个" clamp"数字类型的函数:Int
,Double
,Float
等
(如果它适用于其他事情,比如字符串,这也很好,但这不是我的目标。)
This演示了隐式Ordering
的专业化仍会对参数进行框/取消装箱。
但它从未确定是否存在解决方案,也许是不接受任意Ordering
。
这会有用吗?
def clamp[@specialized A <% Ordered[A]](low: A, high: A)(value: A) =
if(low > value) {
low
} else if(high < value) {
high
} else {
value
}
或<%
导致拳击和拆箱?
如果是的话,是为每种原始类型写一个单独的函数我唯一的办法吗?
编辑:有一个类似意图的问题 - How to write a limit function in Scala? - 虽然它既不是泛型也不是专业化。
答案 0 :(得分:2)
拳击仍然发生。
<%
表示&#34;可通过隐式转换转换&#34;,在这种情况下,它意味着类型A => Ordered[A]
的附加隐式参数。为了调用<
方法,代码必须将数字包装到Ordered[A]
对象中,如果原语是类scala.runtime.Rich***
。专业化无法猜测<
整数或双精度是否比这更具体。
此外,A=>Ordered[A]
还需要盒装输入,因为Function1
并非专门针对案例primitive=>reference
,仅针对primitive=>primitive
。
因此拳击会发生两次。
使用-print
进行编译会产生以下结果:
<specialized> def clamp$mIc$sp(low: Int, high: Int, value: Int, evidence$1: Function1): Int =
if (evidence$1.apply(scala.Int.box(low)).$asInstanceOf[math.Ordered]().>(scala.Int.box(value)))
low
else
if (evidence$1.apply(scala.Int.box(high)).$asInstanceOf[math.Ordered]().<(scala.Int.box(value)))
high
else
value;
Int.box
将整数设置为java.lang.Integer
,evidence$1.apply
将其解包并将其重新装入scala.runtime.RichInt
。
我建议手动专门编写代码。
答案 1 :(得分:1)
你可以用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
。这依赖于一些变量,但是可变状态完全包含在宏的主体内,并且在调用时可以安全地忽略。
一旦写入,宏可以被外部代码中的普通函数引用:
// 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语句直接插入代码,无论它在何处使用。它还需要创建一些临时变量并进行布尔检查以防止对输入进行多重评估,但这些应该是非常低的影响。只要知道类型(它几乎总是可以推断出来),它就可以使用文字和运行时值。