在我的应用中,我正在跟踪用户拥有的积分数量。要添加一些类型检查,我使用类似于这个的Credits
类:
case class Credits(val numCredits: Int) extends Ordered[Credits] {
...
}
假设我有一个我想要调用的函数def accept(creds: Credits): Unit
。有没有办法让我用
process(Credits(100))
process(0)
但不是这个?
process(10)
即,我只想从文字0
提供隐式转换,而不是其他。现在,我在伴侣对象中只有val Zero = Credits(0)
,我认为这是一个相当不错的练习,但无论如何我都会对答案感兴趣,包括其他评论,例如:
Credits
而不是扩展AnyVal而不是2.10中的案例类?答案 0 :(得分:10)
这种编译时检查是使用宏的好地形,将在2.10中提供
一个名叫 Jason Zaugg 的聪明人已经实现了类似于你需要的东西,但它适用于正则表达式:正则表达式编译时检查。
您可能希望查看其Macrocosm以了解它是如何完成的,以及如何以相同的目的编写自己的宏。
https://github.com/retronym/macrocosm
如果你真的想了解更多有关宏的知识,首先我会说你需要勇敢,因为现在文档很少,而API很可能会改变。 Jason Zaugg的作品与2.10-M3合作很好,但我不确定它是否适用于较新的版本。
如果你想从一些读数开始:
一个很好的切入点是scalamacros网站http://scalamacros.org/和SIP文档https://docs.google.com/document/d/1O879Iz-567FzVb8kw6N5OBpei9dnbW0ZaT7-XNSa6Cs/edit?pli=1
如果你有时间,你可能还想阅读Eugene Burmako的演讲: http://scalamacros.org/talks/2012-04-28-MetaprogrammingInScala210.pdf
现在,谈到主题,Scala宏是 CAT :“编译时AST转换”。 抽象语法树是编译器表示源代码的方式。编译器将后续转换应用于AST,并在最后一步实际生成java字节码。
现在让我们来看看Jason Zaugg的代码:
def regex(s: String): scala.util.matching.Regex = macro regexImpl
def regexImpl(c: Context)(s: c.Expr[String]): c.Expr[scala.util.matching.Regex] = {
import c.universe._
s.tree match {
case Literal(Constant(string: String)) =>
string.r // just to check
c.reify(s.splice.r)
}
}
正如您所见,正则表达式是一个特殊的函数,它通过调用宏regexImpl接受一个字符串并返回一个正则表达式
宏函数在第一个参数列表中接收上下文,在第二个参数列表中以c.Expr [A]的形式列出宏的参数并返回c.Expr [B]。请注意,c.Expr是路径依赖类型,即它是在Context中定义的类,因此如果您有两个上下文,则以下内容是非法的
val c1: context1.Expr[String] = ...
val c2: context2.Expr[String] = ...
val c3: context1.Expr[String] = context2.Expr[String] // illegal , compile error
现在,如果你看一下代码中发生了什么:
这里发生的是从Predef.scala中定义的字符串到StringOps的隐式转换,它会在每个scala源的编译中自动导入
implicit def augmentString(x: String): StringOps = new StringOps(x)
StringOps扩展了scala.collection.immutable.StringLike,其中包含:
def r: Regex = new Regex(toString)
由于宏是在编译时执行的,所以这将在编译时执行,如果抛出异常,编译将失败(这是从无效的正则表达式字符串创建正则表达式的行为)
注意:不幸的是,API非常不稳定,如果查看http://scalamacros.org/documentation/reference.html,您会看到Context.scala链接断开。正确的链接是https://github.com/scala/scala/blob/2.10.x/src/reflect/scala/reflect/makro/Context.scala
答案 1 :(得分:3)
基本上,您需要dependent types。为什么Scala在路径依赖类型中支持有限形式的依赖类型,它无法满足您的要求。
Edmondo在建议宏方面有一个好主意,但它有一些局限性。由于它非常简单,我实现了它:
case class Credits(numCredits: Int)
object Credits {
implicit def toCredits(n: Int): Credits = macro toCreditsImpl
import scala.reflect.makro.Context
def toCreditsImpl(c: Context)(n: c.Expr[Int]): c.Expr[Credits] = {
import c.universe._
n.tree match {
case arg @ Literal(Constant(0)) =>
c.Expr(Apply(Select(Ident("Credits"), newTermName("apply")),
List(arg)))
case _ => c.abort(c.enclosingPosition, "Expected Credits or 0")
}
}
}
然后我启动了REPL,定义了accept
,并进行了基本演示:
scala> def accept(creds: Credits) { println(creds) }
accept: (creds: Credits)Unit
scala> accept(Credits(100))
Credits(100)
scala> accept(0)
Credits(0)
scala> accept(1)
<console>:9: error: Expected Credits or 0
accept(1)
^
现在,问题是:
scala> val x = 0
x: Int = 0
scala> accept(x)
<console>:10: error: Expected Credits or 0
accept(x)
^
换句话说,我无法跟踪分配给标识符的值的属性,这是依赖类型允许我这样做的。
但是整个事情让我感到浪费。为什么要转换只 0?您似乎想要一个默认值,在这种情况下,最简单的解决方案是使用默认值:
scala> def accept(creds: Credits = Credits(0)) { println(creds) }
accept: (creds: Credits)Unit
scala> accept(Credits(100))
Credits(100)
scala> accept()
Credits(0)
答案 2 :(得分:1)
使用可以使用隐式部分函数:
scala> case class Credits(val numCredits: Int)
defined class Credits
scala> def process(c: Credits) = {}
process: (c: Credits)Unit
scala> implicit def i2c:PartialFunction[Int, Credits] = { case 0 => Credits(0) }
i2c: PartialFunction[Int,Credits]
允许你
scala> process(Credits(12))
和
scala> process(0)
可是:
scala> process(12)
scala.MatchError: 12 (of class java.lang.Integer)
at $anonfun$i2c$1.apply(<console>:9)
at $anonfun$i2c$1.apply(<console>:9)
at .<init>(<console>:12)
at .<clinit>(<console>)
at .<init>(<console>:11)
at .<clinit>(<console>)
at $print(<console>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.sca
la:920)
at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:4
3)
at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
at java.lang.Thread.run(Unknown Source)
编辑:但是,编译器仍会允许process(12)
在运行时导致匹配错误。