2013-04-23 68 views
4

我一直在Scala中做一些練習。我想我可能會嘗試推導出一種方法,使用新添加的AnyVal特徵來創建不相容的值類型,這些類型不會被意外地分配給對方。在Scala中對構造函數進行通用化處理

我能想出的最好的是這樣的:

object Measurements { 
    trait ValueType[T] extends Any { 
    def value: T 
    } 

    trait Measurement[A <: ValueType[Double]] extends Any { 
    def modify(fn: (Double, A) => Double, value: A): A 
    def +(mod: A) = modify((x: Double, y: A) => x + y.value, mod) 
    def -(mod: A) = modify((x: Double, y: A) => x - y.value, mod) 
    def *(mod: A) = modify((x: Double, y: A) => x * y.value, mod) 
    def /(mod: A) = modify((x: Double, y: A) => x/y.value, mod) 
    } 

    case class Frequency(value: Double) extends AnyVal 
     with ValueType[Double] 
     with Measurement[Frequency] 
    { 
    def modify(fn: (Double, Frequency) => Double, mod: Frequency) 
     = Frequency(fn(value, mod)) 
    } 

    case class Amplitude(value: Double) extends AnyVal 
     with ValueType[Double] 
     with Measurement[Amplitude] 
    { 
    def modify(fn: (Double, Amplitude) => Double, mod: Amplitude) 
     = Amplitude(fn(value, mod)) 
    } 

    case class Wavelength(value: Double) extends AnyVal 
     with ValueType[Double] 
     with Measurement[Wavelength] 
    { 
    def modify(fn: (Double, Wavelength) => Double, mod: Wavelength) 
     = Wavelength(fn(value, mod)) 
    } 
} 
import Measurements._ 
Frequency(150) + Frequency(10) // ==> Frequency(160) 
Amplitude(23.2) * Amplitude(2) // ==> Amplitude(46.4) 
Amplitude(50) + Frequency(50) // ==> Compile-time Type Error 

不幸的是它需要我唯一確定的modify功能爲每個實例,因爲這是不可能的定義類似A(value)與泛型類型A 。似乎沒有辦法來定義構造函數約束。否則,我或許能夠確定共同的東西在性狀,如:

def modify(fn: (Double, A) => Double, mod: A) = A(fn(value, mod)) 

我試過呼籲Aapply(Double),但它不是從一個普通的變量訪問。我也試着想看看能不能做一些工廠來至少簡化一些事情,但卻無法提出比我現在所做的更優雅的事情。我總是遇到與C#相同的問題。

是否有某種方法可以將那些依賴於不同(但相關)類的公共構造函數類型的代碼分解出來?

+0

通過使用類型類,您的程序可以在沒有反射的情況下得到改進。看看[丹尼爾韋斯特海德關於這個主題的文章,來自他傑出的Scala_的指南](http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type- classes.html)。而且,儘管這可能與此無關,請查看[Squants](http://squants.com),這是一個「數據類型框架和域特定語言(DSL)」,用於表示數量及其度量單位及其尺寸關係。「 – 2017-12-06 17:41:09

回答

4

我不認爲這是可能的,而不訴諸於運行時反射(或可能是宏)。有三種基本的問題,而你已經指出二:

  • 這是不可能的特質申報的強制性構造函數簽名。

  • 不可能在A上調用諸如apply等方法,因爲它是一個類型變量而不是類或對象。

  • 由於值類可以(目前)僅延伸universal traits,但不是抽象類,這是不可能的,爲了得到屬於類型A被實例化與當Measurement被擴展的類使用TypeTags

我能想到的最好方法是以下方法。請注意,它使用反射,並且如果Measurement的具體實例沒有聲明適當的構造函數,則可以在運行時拋出異常。

// ... as above ... 

trait Measurement[A <: ValueType[Double]] extends Any { self: A => 
    def modify(fn: (Double, A) => Double, mod: A): A = 
    this.getClass 
     .getConstructor(this.getClass) 
     .newInstance(fn(value, mod): java.lang.Double) 

    // ... as above ... 
} 

case class Frequency(value: Double) 
    extends AnyVal 
     with ValueType[Double] 
     with Measurement[Frequency] 

// ... etc ... 

Frequency(150) + Frequency(10) // ==> Frequency(160) 
Amplitude(23.2) * Amplitude(2) // ==> Amplitude(46.4) 
Amplitude(50) + Frequency(50) // ==> Compile-time Type Error 

自類型的註釋self: A確保由ValueType聲明的value場是從裏特質Measurement訪問。

+0

我有點擔心是這種情況(任何人花了兩天的時間甚至試圖回答)。反射解決方案確實有效,但通常以犧牲性能和安全爲代價,這是一種恥辱。儘管如此,作爲參考還是不錯的。+1 w/accept – KChaloux 2013-04-25 15:10:41