2011-01-08 51 views
3

我發現我可能使用使用隱式轉換宣佈和強制先決條件。試想一下:我是否應該使用隱式轉換來強制執行前提條件?

object NonNegativeDouble { 
    implicit def int2nnd(d : Double) : NonNegativeDouble = new NonNegativeDouble(d) 
    implicit def nnd2int(d : NonNegativeDouble) : Double = d.v 
    def sqrt(n : NonNegativeDouble) : NonNegativeDouble = scala.math.sqrt(n) 
} 

class NonNegativeDouble(val v : Double) { 
    if (v < 0) { 
    throw new IllegalArgumentException("negative value") 
    } 
} 

object Test { 
    def t1 = { 
    val d : Double = NonNegativeDouble.sqrt(3.0); 
    printf("%f\n", d); 
    val n : Double = NonNegativeDouble.sqrt(-3.0); 
    } 
} 

暫時忽略例子的實際虛標:我的觀點是,子類NonNegativeDouble表達了一個功能只類的值的整個範圍的子集的概念。

首先是這樣的:

  1. 一個好主意,
  2. 一個壞主意,或
  3. 明顯的想法別人都已經知道

其次,這將是最有用的使用基本類型,如Int和String。當然,這些類是最終的,那麼是否有一種好方法不僅可以在函數中使用受限類型(這是第二種隱含的方式),還可以將所有方法委託給基礎值(缺少實現每個委託)?

+0

我發現了一種方法,如何在csharp中做到這一點。這是我的原型https://gist.github.com/1306491 – 2011-10-23 09:51:13

回答

9

這是一個非常酷的想法,但遺憾的是其真正的潛力不能在Scala的類型系統來實現。您真正需要的是dependent types,它允許您對方法的調用者施加證明義務來驗證參數是否在範圍內,因此該方法甚至無法用無效參數調用

但是,如果沒有依賴類型和在編譯時驗證規範的能力,我認爲這具有可疑的價值,即使不考慮性能方面的考慮因素。考慮,它是如何更好比使用require功能寫明您的方法所需的初始條件,就像這樣:

def foo(i:Int) = { 
    require (i >= 0) 
    i * 9 + 4 
} 

在這兩種情況下,負值將導致異常在運行時被拋出,無論是在require功能或構建您的NonNegativeDouble。兩種技術都清楚地說明了方法的契約,但我認爲在構建所有這些專用類型時存在很大的開銷,其唯一目的是封裝要在運行時斷言的特定表達式。例如,如果你想強制執行一個稍微不同的先決條件,說,那i > 45?你會爲此方法建立一個IntGreaterThan45類型嗎?

我可以看到建立例如一個NonNegativeFoo類型是如果你有許多方法只消耗和返回正數。即使那樣,我認爲收益是可疑的。

順便說一下,這與問題How far to go with a strongly typed language?類似,我也給出了類似的答案。

+0

如果沒有解決你的任何*真實*問題,讓我回答你的簡單問題。首先,爲什麼這樣做而不是`require'?我會忽略我從來沒有聽說過的「需要」的基本現實,並且提到(a)這樣更加確定和更簡潔,並且(b)這種方式與功能的用戶進行通信,而不是假設他閱讀手冊,他沒有。其次,我希望能夠將範圍限制表示爲NumberGreaterThan [45]`,但我認爲我不能。嘿,從這個角度來看C++比Scala更好!我們應該寫Bjorne Stroustrop並告訴他。 – Malvolio 2011-01-08 15:02:49

0

實際上是一個很整齊的想法,雖然我不會在任何性能敏感的循環中使用它。

@specialisation也可以幫幫忙,用了相當數量的爲您提供幫助使代碼更高效......

0

這在C中通常被稱爲「unsigned int」。我不認爲它非常有用,因爲您無法正確定義運算符。考慮這個:

val a = UnsignedInt(5) 
val b = a - 3 // now, b should be an UnsignedInt(2) 
val c = b - 3 // now, c must be an Int, because it's negative! 

因此,你會如何定義負運算符?這可能是:

def -(i:Int):Either[UnsignedInt,Int] 

這將使算術UnsignedInt實際上無法使用。

或者你定義了一個超類MaybeSignedInt,它有兩個子類SignedIntUnsignedInt。然後,你可以在UnsignedInt這樣定義減法:

def -(i:Int):MaybeSignedInt 

似乎完全可怕的,不是嗎?實際上,號碼的符號在概念上不應該是類型的屬性,而應該是它的價值。

+0

'unsigned int`的C概念*不是`int`的受限範圍。例如,2,147,483,649是一個unsigned int(在32位機器中),但它不是int。算術運算不會在子集上關閉,所以在我的系統中,即使在兩個NonNegativeInts上也會返回一個Int。事實上,他們可以在任何計算機語言中被視爲封閉的常規數字類型的唯一方式是擴展類型(以包含像`NaN`)*和*接受只能被描述爲錯誤答案的類型(如溢出,下溢,和精度損失)。 – Malvolio 2011-01-08 15:11:37

相關問題