2014-01-20 37 views
9

鑑於how difficult it is to know whether an arithmetic final val expression will be compiled to a compile-time constant, and how easy it is to accidentally break compile-time-ness ......有沒有辦法在編譯時測試一個常量是編譯時常量?

誰能想到一個簡單的方法來驗證,在編譯時,編譯器實際上已經創造了一個編譯時間,比如說,從一個複雜的算術表達式不變?我猜這可能是某種註解或宏,但也許有一些更簡單。例如,可能是這樣的:

@CompileTime final val HALF_INFINITY = Int.MaxValue/2 

將成爲可能。

回答

7

Luckil足夠的話,宏被連接到類型檢查(宏觀參數在宏擴展之前進行類型檢查)和類型檢查摺疊常量,因此它看起來應該足以在宏中檢查Literal(Constant(_))以確保宏的參數是一個常數。

注意。宏觀天堂中實施的宏註釋在對註冊人進行類型檢查之前進行了擴展,這意味着他們的觀點在擴展期間不會被固定,使得宏觀註釋成爲執行此任務的不太方便的工具。

下面是使用Scala 2.11.0-M8語法爲def宏編寫的代碼。對於2.11.0-M7,請用import scala.reflect.macros.{BlackboxContext => Context}替換導入。對於2.10.x,將導入替換爲import scala.reflect.macros.Context,將impl的簽名重寫爲def impl[T](c: Context)(x: c.Expr[T]) = ...,將ensureConstant的簽名改爲def ensureConstant[T](x: T): T = macro impl[T]

// Macros.scala 

import scala.reflect.macros.blackbox._ 
import scala.language.experimental.macros 

object Macros { 
    def impl(c: Context)(x: c.Tree) = { 
    import c.universe._ 
    x match { 
     case Literal(Constant(_)) => x 
     case _ => c.abort(c.enclosingPosition, "not a compile-time constant") 
    } 
    } 

    def ensureConstant[T](x: T): T = macro impl 
} 

// Test.scala 

import Macros._ 

object Test extends App { 
    final val HALF_INFINITY = ensureConstant(Int.MaxValue/2) 
    final val HALF_INFINITY_PLUS_ONE = ensureConstant(HALF_INFINITY + 1) 
    final val notConst = ensureConstant(scala.util.Random.nextInt()) 
} 

00:26 ~/Projects/Master/sandbox (master)$ scalac Macros.scala && scalac Test.scala 
Test.scala:6: error: not a compile-time constant 
     final val notConst = ensureConstant(scala.util.Random.nextInt()) 
             ^
one error found 
2

你把你的問題說成是關於確定一個值是否在參考點上在線擴展,但似乎你實際上正在尋找一種方法來保證它。那是對的嗎?

如果您將它製作爲def,並對其進行內嵌擴展註釋(@inline),則可能會得到您想要的結果。

@inline def TwentyTwo = 22 
+1

1.您可以在這裏使用'val':'@inline val TwentyTwo = 22'。 2.不,它不會使'TwentyTwo'在java字節碼方面保持不變。試試'@inline val j = util.Random.nextInt()' - 它絕對不是編譯時常量。 – senia

+0

是的,謝謝,我想要一個方法來保證它。我正在做一些需要極高性能的大集合的算術密集型工作。所以我想確保編譯時可以訪問該常量的值。 –

+0

很顯然,如果方法體中有參數或副作用,它不會是任何你會稱之爲常量的東西,但我認爲提問者不會這樣做,它會使問題的整個前提無效。 –

3

我想這是不可能的,即使宏:

import scala.reflect.macros.Context 
import scala.language.experimental.macros 

def showMacroImpl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { 
    import c.universe._ 

    val inputs = annottees.map(_.tree).toList 

    println(inputs.map{showRaw(_)}) 

    c.Expr[Any](Block(inputs, Literal(Constant(())))) 
} 


import scala.annotation.StaticAnnotation 
class showMacro extends StaticAnnotation { 
    def macroTransform(annottees: Any*) = macro showMacroImpl 
} 

object Test { 
    @showMacro final val i = 1+1 
    @showMacro final val j = util.Random.nextInt() 
    @showMacro final val k = Int.MaxValue/2 
} 
// List(ValDef(Modifiers(FINAL), newTermName("i"), TypeTree(), Apply(Select(Literal(Constant(1)), newTermName("$plus")), List(Literal(Constant(1)))))) 
// List(ValDef(Modifiers(FINAL), newTermName("j"), TypeTree(), Apply(Select(Select(Ident(newTermName("util")), newTermName("Random")), newTermName("nextInt")), List()))) 
// List(ValDef(Modifiers(FINAL), newTermName("k"), TypeTree(), Apply(Select(Select(Ident(newTermName("Int")), newTermName("MaxValue")), newTermName("$div")), List(Literal(Constant(2)))))) 

ijk這裏沒有什麼區別。

你會得到任何信息,即使scalac -Xprint:cleanup test.scala

final <stable> <accessor> def i(): Int = 2; 
final <stable> <accessor> def j(): Int = Test.this.j; 
final <stable> <accessor> def k(): Int = 1073741823; 

你只能從.icode文件中獲取此信息(scalac -Xprint:all test.scala; cat Test\$.icode):

def i(): Int(2) { 
locals: 
startBlock: 1 
blocks: [1] 

1: 
    2 CONSTANT(2) 
    2 RETURN(INT) 

} 

def k(): Int(1073741823) { 
locals: 
startBlock: 1 
blocks: [1] 

1: 
    4 CONSTANT(1073741823) 
    4 RETURN(INT) 

} 

或者從Java字節碼(javap -c Test\$.class):

public final int i(); 
    Code: 
    0: iconst_2  
    1: ireturn  

public final int k(); 
    Code: 
    0: ldc   #21     // int 1073741823 
    2: ireturn  
+0

A努力。我認爲做一個有點醜陋的做法可能是一個以下常用註釋。據我瞭解,至少在Java中,註釋的整數參數必須是編譯時常量。醜陋的部分是,AFAIK,註釋必須有某種虛擬的「主題」。 –

+0

@EdStaub:請注意'Test.i'不是一個常量,它是一個返回常量的方法。 – senia

+0

哎喲,是的,謝謝,我錯過了它(即Test.i是一種方法)。我想我需要查看val的潛在編譯時使用情況,以查看該方法是否被調用,或者編譯器將其短路並僅插入常量值。如果沒有跳過,我的問題是沒有意義的。 –