2014-08-27 50 views
1

我想要實現的數字類型「夾」功能:IntDoubleFloat是否可以使用@specialized Ordered?

(如果它適用於其他東西,比如字符串,那也沒關係,但是這不是我的目標。)

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? - 儘管它要求既沒有仿製藥,也沒有專業化。

回答

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) 
    } 
} 

注:基於卡羅爾小號的建議下,我已經改寫了宏觀評估其論據儘可能少。如果lowhighvalue的輸入很貴,則只會評估lowvalue一次。 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語句直接插入代碼中的任何地方。它還需要創建一些臨時變量並進行布爾檢查以防止對輸入進行多重評估,但這些影響應該很低。它可以處理文字和運行時值,只要它知道類型(它幾乎總能推斷出來)。

+0

這個宏不會多次評估它的參數嗎? – 2014-08-27 23:07:51

+0

@KarolS正確。我用一個只評估一次參數的版本更新了答案(如果可能,跳過評估「高」輸入)。它膨脹了一些代碼,但很好地處理你的輸入是昂貴的功能的情況。感謝您的意見。它可能會進一步優化,以檢測輸入是否是文字,並在編譯時實際進行鉗位,但我不認爲這將是典型的用例。 – KChaloux 2014-08-28 15:31:18

2

拳擊仍然發生。

<%表示「可通過隱式轉換進行轉換」,在這種情況下表示類型爲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.Integerevidence$1.apply再次unboxes它和包裝盒成scala.runtime.RichInt

我建議專門用手工代碼。

+0

是的,我懷疑我必須這樣做。在這些情況下,C++模板真的很耀眼。 – 2014-08-27 15:34:30