2012-10-07 36 views
6

鑑於這樣的:提取方法,以獨立的功能斯卡拉

class A { 
    def f(x: X) = ... 
    def g(y: Y, z: Z) = ... 
    ... 
} 

如何(自動)提取功能(S):

object A { 
    val f' = (a: A, x: X) => a.f(x) // do this automagically for any arbitrary f 
    val g' = (a: A, y: Y, z: Z) => a.g(y, z) // and deal with arity > 1 too 
    ... 
} 

有了這個確切類型簽名(第一對象,然後是參數列表)。讓我來說明這個問題很清楚:

「鑑於一類A的上下文中定義的方法f(x1: X1, ..., xn: Xn),如何自動提取f'(I) 接收A類型的實例a功能和(ⅱ)對應1參數列表:1至f參數列表,即x1: X, ... xn: Xn,其實現是恰好a.f(x1: X1, ..., xn: Xn)

甚至:

捕獲的λ演算外延性,這樣 的概念,它可以自動提取λx.(f x)f每當xf出現免費。

這可能首先找到一種方法訪問標識符fg,......不具有特定a: A來解決(一個將有一個特定的A後,當然)。我們可以簡單地用手寫f'g',但讓我們放縱DRYness。


P.S.也許這是不可能的,沒有運行時反射(雖然它可以用Scala 2.10+宏),因爲我似乎無法事先找到指定fg而沒有特定實例(a: A)的標識符的方法。但它會像下面這樣,而不必訴諸strings

A.getMethod("f"): Function2[A, X, ...] 

我也意識到了問題的是實際應用可以幫助學員提出的替代品,但我在抽象的意義上討論這個。我並沒有試圖解決我減少到這個問題的其他問題。我想知道如果這一個是可能的:-)這是一個very nice article真正理解這個問題背後的動機,與Scala上的Eta擴展咆哮。

+0

使用反射這樣的目的,實在不是一個好主意。它將不可避免地變成過度複雜,它會降低性能,並且會破壞使用靜態語言的整個觀點,因爲所有的方法解析和調用都將在運行時完成。你應該真的考慮不同的方法來解決你的問題,比如使用implicits。可能用適當的信息擴展你的問題可能會幫助其他人幫助你。 –

+0

@NikitaVolkov我同意你的意見。反思很糟糕。但如果在編譯時解決問題,那麼就沒有理由降低性能,也不會丟失類型安全性。 –

+0

反射無法在編譯時解決,因爲它的全部內容是在運行時反省您的程序。從2.10開始,Scala中有一個宏特性,但是在編譯時能夠解決你的問題,這絕對是一種矯枉過正的現象。我不是這方面的專家,所以我無法幫助你。順便說一下,在目前的狀態下,你的問題非常廣泛,很可能會被封閉。很難得到你在這裏問的實際內容。 –

回答

2

您當然可以在編譯時使用宏來做到這一點 - 我在the preview release of ScalaMock 3中做了非常類似的事情 - 模擬對象是匿名類的實例,其中每個成員都由一個模擬函數實例實現。你可能會把它作爲你想要做的事情的起點。

警告:ScalaMock 3目前只適用於Scala 2.10.0-M6。它不適用於M7或當前的開發版本,原因是我沒有(還沒有!)有機會解決宏API中發生的變化。

+0

感謝您的反饋!你可以使用一些提示嗎? :-) –

+0

我不敢肯定,我真的可以說更多,這恐怕很有幫助。我確信你想要做的事情是可能的,但這不會是微不足道的。我懷疑,就像我在ScalaMock中必須做的那樣,你可能需要「手動」構建你想要的樹,並且你將遇到許多與處理相關的複雜問題類型參數化和重載方法。要查看的源文件是https://github.com/paulbutcher/ScalaMock/blob/develop/core/src/main/scala/org/scalamock/Mock.scala - 我懷疑你可以用它作爲一個起點。 –

+0

你也應該知道,這個領域目前非常流行。我剛纔提到的代碼與M6相對,但是全面地針對M7進行了破解(甚至在2.10的當前開發版本中破壞了該代碼)。 –

0

這與Lensed項目類似,該項目爲Scala案例類創建lenses。我相信可以修改它來創建您描述的方法而不是鏡頭。

+1

不幸的Lensed使用了一個編譯器插件。正如我從ScalaMock 2的慘痛經驗中得知的那樣,編譯器插件有很大的侷限性(這就是ScalaMock 3轉向使用宏的原因) –

-2

我知道這不是一個非常漂亮的解決方案,但是如果您不能使用宏,您可以爲您需要的伴隨對象生成源代碼並在單獨的構建步驟中對其進行編譯。

def generateCompanionObject(clazz: Class[_]) = { 
    val className = clazz.getName.split("\\.").last 
    val firstMethod = clazz.getMethods.head//for simplicity 
    val methodName = firstMethod.getName 
    val parametersClasses = firstMethod.getParameterTypes.map(_.getName).toSeq 

    val objectDefinition = "object " + className + " {\n" + 
     generateMethodDefinition(className, methodName, parametersClasses) + 
     "\n}" 

    objectDefinition 
} 

def generateMethodDefinition(className: String, methodName: String, parameterClasses: Seq[String]) = { 
    val newMethodName: String = " def invoke" + methodName.capitalize + "On" 

    val parameterList: String = "(o:" + className + ", " + parameterClasses.zipWithIndex.map { 
     case (argClassName, index) => "arg" + index + ": " + argClassName 
    }.mkString(", ") + ")" 

    val generateOldMethodCall: String = "o." + methodName + parameterClasses.zipWithIndex.map { 
     case (argClassName, index) => "arg" + index 
    }.mkString("(", ",", ")") 

    newMethodName + parameterList + " = " + generateOldMethodCall 
} 

class A { 
    def foo(x: String, y: String) = x + y 
} 

類就會產生

object A { 
    def invokeFooOn(o:A, arg0: java.lang.String, arg1: java.lang.String) = o.foo(arg0,arg1) 
}