2010-03-14 78 views
4

假設我正在編寫一個函數,它接受一個整數列表,並且只返回列表中小於5.2的整數。我可能會這樣做:Haskell:在Double類型上約束函數只能用於整數

belowThreshold = filter (< 5.2) 

很簡單吧?但是現在我想限制這個函數只能用於我自己設計理由的輸入列表[Int]。這似乎是一個合理的要求。唉,不。一種限制類型的聲明如下:

belowThreshold :: [Integer] -> [Integer] 
belowThreshold = filter (< 5.2) 

導致類型錯誤。那麼這裏有什麼故事?爲什麼做過濾器(< 5.2)似乎將我的輸入列表轉換爲雙精度?我怎樣才能使這個函數的版本只接受整數列表並只返回整數列表?爲什麼類型系統恨我?

+4

任何理由不'濾波器(<= 5)'?對於整數,這是完全相同的。 – 2010-03-14 16:36:18

回答

8

Check加入您的annoatation之前在ghci中推斷出的類型belowThreshold的:

> :t belowThreshold 
belowThreshold :: [Double] -> [Double] 

這聽起來像你預期Num a => [a] -> [a]當你說 「約束這個功能」。添加[Integer] -> [Integer]註釋時,實際上是在更改函數的類型。

爲了使這項工作,使用顯式轉換:

belowThreshold = filter ((< 5.2) . fromIntegral) 

現在belowThreshold :: [Integer] -> [Integer]像你想要的。但整數在與5.2比較之前轉換爲雙打。

那麼,爲什麼你需要轉換?類型錯誤可能會誤導你:整數列表並未被轉換爲雙精度,與5.2相比,真正的問題在於,只有雙打可以比作雙精度,所以你必須要把雙精度列表傳給belowThreshold 。 Haskell沒有隱式轉換,甚至沒有數字之間的轉換。如果你想轉換,你必須自己寫。

我想約束這個函數只適用於我自己設計理由的[Int]類型的輸入列表。這似乎是一個合理的要求。

那麼,從類型系統的角度來看,沒有。這是合理的代碼?

'c' < "foo" 

這是怎麼回事?

12 < "bar" 

所有這些值是Ord實例,但你不能用(<)一起使用。 Haskell沒有隱式轉換。因此,即使兩個值都是Num以及Ord的實例,如果它們的類型不同,您將無法將它們與(<)進行比較。

+0

實際上,Haskell對數字文字有一點隱式轉換。 「x = 5」相當於「x = fromIntegral 5」。 – 2010-03-15 22:06:28

+0

我認爲你已經倒退了。在ghci中,':t 5'給出'(Num t)=> t'和':t fromIntegral'給出'(積分a,數字b)=> a - > b'。所以'fromIntegral 5'根本不會改變類型。 然而'let x = 5'然後':t x'給出'Integer'。發生了什麼事是變量必須有一個類型,所以ghci只是選擇Integer。這不是一種轉換,而是OP的思想發生在原始代碼中的'縮小' - 從'Num t'到'Integer'。 – 2010-03-16 14:05:52

3

您試圖將整數與double(5.2)進行比較。 Haskell不喜歡那樣。嘗試使用

filter (< 6) 
+0

我想這是現​​實世界中的正確答案。在完美的世界中,我可以縮小函數的簽名而無需更改實現細節。 – 2010-03-14 17:11:49

+0

真的嗎?類型的變化通常意味着實現中的變化:http://en.wikipedia.org/wiki/Curry-Howard_correspondence – 2010-03-14 18:14:33

+2

@thurn:我不會聲稱將類型從Double更改爲Integer會「縮小」。整數並不是Double的子類型,它們的元素結構不同,它們遵守不同的公理和定理等。 此外,還有比規範雙精度更多的規範整數。 – fishlips 2010-03-14 19:16:52

1

如果必須使用雙(讓我們說這是一種說法),我會用ceiling

filter (< (ceiling 5.2))

現在,如果你想有一個函數,它的邊界值作爲'任何'(相關)數字值,您可以使自己的類型類別爲您設置最大數量。

class Ceilingable a where 
    ceil :: (Integral b) => a -> b 

instance (RealFrac a) => Ceilingable a where 
    ceil = ceiling 

instance (Integral a) => Ceilingable a where 
    ceil = fromIntegral 

belowThreshold :: (Ceilingable a) => a -> [Integer] -> [Integer] 
belowThreshold threshold = filter (< ceil threshold) 
1

語法5.2適用於任何FractionalInt不是Fractional的實例,也不能或不應該。如何將任意Rational轉換爲Int時的操作未指定。

從一個任意分數轉換爲Double然而,這是非常有意義的(在該類型的範圍內)。

您的期望是由許多語言中的隱式強制的存在所驅動的。

但是,那些帶有成本。您必須手動確保整個系統的強制合併。 Haskell不這樣做,而是選擇讓數字文字語法利用類型系統。要在它們之間進行轉換,您需要使用fromIntegral來明確是否需要強制轉換,這樣可以避免依賴匯合並允許程序員定義新的數字類型。

belowThreshold = filter (\x -> fromIntegral x < 5.2) 

這類似於在C++中使用顯式轉換,如((double)x < 5.2)。雖然,這種說法只能是因爲違約的,因爲5.2可以用作任何Fractional成員,以及「fromIntegral X」結果是任何NumFractional父類,因此fromIntegral x < 5.2是尚未,它僅僅知道它需要比較相同類型的兩個Fractional值,並根據「默認」語句選擇Double作爲合理的默認值。

還要注意的是Int不是唯一Integral類型,因此,上述方法適用Integral值的任何列表上:

belowThreshold :: Integral a => [a] -> [a]