2013-02-02 149 views
2

我試圖圍繞Haskell類型強制包裹我的頭。意思是什麼時候可以將一個值傳遞給一個函數而不需要投射以及它是如何工作的。這裏是一個具體的例子,但我在尋找我可以用前進,試圖瞭解正在發生的事情更一般的解釋是:Haskell類型強制

Prelude> 3 * 20/4 
15.0 
Prelude> let c = 20 
Prelude> :t c 
c :: Integer 
Prelude> 3 * c/4 

<interactive>:94:7: 
    No instance for (Fractional Integer) 
     arising from a use of `/' 
    Possible fix: add an instance declaration for (Fractional Integer) 
    In the expression: 3 * c/4 
    In an equation for `it': it = 3 * c/4 

的類型(/)是分數A => A - > a - > a。所以,我猜測,當我使用文字做「3 * 20」時,Haskell以某種方式假定該表達式的結果是分數。但是,如果使用變量,則根據賦值將類型預定義爲Integer。

我的第一個問題是如何解決這個問題。我需要轉換表達式還是以某種方式轉換它? 我的第二個問題是,對於我來說,這看起來很奇怪,因爲如果不用擔心int/float類型的問題,就無法進行基本的數學運算。我的意思是有一個顯而易見的方法可以在這些之間自動轉換,爲什麼我不得不考慮這個並處理它?我開始做錯了什麼?

我基本上正在尋找一種方法來輕鬆地編寫簡單的算術表達式,而不必擔心整潔的偉大的細節,並保持代碼的漂亮和乾淨。在大多數頂級語言中,編譯器爲我工作 - 而不是相反。

+1

快速提示:請嘗試在源文件中執行此操作,而不是直接在GHCi中執行此操作。它會以這種方式爲你提供更多的東西,因爲它可以查看整個程序,而不是一次處理一行。 –

+0

這個問題是基於一個不起作用的整個程序。我已經隔離了編譯器正在抱怨的這個特定部分。我可以發佈整個事情,但我認爲它不會有多大幫助。 – oneself

+9

你的問題已被回答,但是:「有一種明顯的方法可以在這些之間自動轉換。」有真的嗎?當我想要一個'Double',或者甚至是一個'Int64'時,'2^128 :: Integer'應該自動變成什麼?當然,由於四捨五入,從非整數類型到整數類型不可能是自動的。一般來說,Haskell沒有子類型或自動類型強制;它具有(約束)參數多態性。但是一旦一個類型變成具體/單形的,那個類型的值就只有*這種類型。 –

回答

11

如果你只是想解決方案,看看結束。

你幾乎已經回答了你自己的問題。在Haskell字面超載:

Prelude> :t 3 
3 :: Num a => a 

由於(*)也有Num約束

Prelude> :t (*) 
(*) :: Num a => a -> a -> a 

這個延伸到產品:

Prelude> :t 3 * 20 
3 * 20 :: Num a => a 

所以,根據上下文,這可以專門是Int類型,IntegerFloatDouble,的和更多,根據需要。特別地,如FractionalNum一個子類,就可以無需在除法問題使用的,但隨後的約束將變得 更強和對Fractional類:

Prelude> :t 3 * 20/4 
3 * 20/4 :: Fractional a => a 

最大的區別是所述標識符c是一個Integer。在GHCi提示符中簡單的let-binding沒有被分配一個重載類型的原因是令人害怕的monomorphism restriction。簡而言之:如果你定義了一個沒有明確參數的值,那麼除非你提供一個明確的類型簽名,否則它不能有重載類型。 數字類型默認爲Integer

一旦cInteger,乘法的結果是Integer,太:

Prelude> :t 3 * c 
3 * c :: Integer 

而且Integer不在Fractional類。

這個問題有兩種解決方案。

  1. 確保您的標識符也有重載類型。在這種情況下,它會 是的話說

    Prelude> let c :: Num a => a; c = 20 
        Prelude> :t c 
        c :: Num a => a 
    
  2. 使用fromIntegral投整數值爲任意數值一樣簡單:

    Prelude> :t fromIntegral 
        fromIntegral :: (Integral a, Num b) => a -> b 
        Prelude> let c = 20 
        Prelude> :t c 
        c :: Integer 
        Prelude> :t fromIntegral c 
        fromIntegral c :: Num b => b 
        Prelude> 3 * fromIntegral c/4 
        15.0 
    
0

Haskell中的類型強制不是自動的(或者說,它實際上並不存在)。當你編寫文字20時,推斷它的類型爲Num a => a(概念上來說,我認爲它不是那麼工作),並且根據它使用的上下文(也就是你傳遞給它的函數)用一個適當的類型實例化(我相信如果沒有進一步的限制,當你需要一個具體的類型時,這個參數默認爲Integer)。如果您需要其他種類的Num,則需要將數字轉換爲在你的例子中爲(3* fromIntegral c/4)

3

這樣Haskell有點不同尋常。是的,你不能一起分爲整數,但它很少是一個問題。

原因是,如果你看看Num typeclass,有一個函數fromIntegral這允許你將文字轉換成適當的類型。這種類型推斷減輕了99%的問題。簡單的例子:

newtype Foo = Foo Integer 
    deriving (Show, Eq) 
instance Num Foo where 
    fromInteger _ = Foo 0 
    negate   = undefined 
    abs    = undefined 
    (+)    = undefined 
    (-)    = undefined 
    (*)    = undefined 
    signum   = undefined 

現在,如果我們這個裝入GHCI

*> 0 :: Foo 
    Foo 0 

*> 1 :: Foo 
    Foo 0 

所以你看,我們能夠做一些與GHCI如何解析原始整數很酷的事情。這在DSL中有很多實際用途,我們不會在這裏討論。

接下來的問題是如何從雙倍到整數或反之亦然。這有一個功能。

從Integer到Double的情況下,我們也會使用fromInteger。爲什麼?

,以及爲它的類型簽名是

(Num a) => Integer -> a 

既然我們可以用(+)與雙打,我們知道他們是一支Num實例。從那裏很容易。

*> 0 :: Double 
    0.0 

最後一塊難題是Double -> Integer。那麼簡單搜索Hoogle節目

truncate 
floor 
round 
-- etc ... 

我會讓你去搜索。

5

哈斯克爾將永遠自動轉換當你把它傳遞給一個函數時,一種類型變成另一種類型它既可以與預期的類型兼容,在這種情況下,不需要強制,或者程序無法編譯。

如果你編寫一個完整的程序並編譯它,通常情況下,這些東西是「正常工作」,而不必考慮關於int/float類型;只要你是一致的(也就是說,你不要試圖把某些東西當作一個Int中的某個東西,而將另一箇中的某個東西當作一個Float),那麼約束只是流過程序併爲你找出類型。

例如,如果我把這個源文件和編譯:

main = do 
    let c = 20 
    let it = 3 * c/4 
    print it 

然後一切都很好,並運行該程序打印15.0。您可以從.0中看到,GHC成功地發現c必須是某種分數,並使所有內容都能正常工作,而不必給出任何明確類型的簽名。

c不能是一個整數,因爲/運算符用於數學除法,它不是整數定義的。整數除法操作由div函數表示(可在操作員方式下用作x `div` y)。我認爲這可能是你在整個項目中絆倒你的原因?不幸的是,如果你習慣了其他語言的情況,那麼/有時是數學分割,有時是整數除法,所以你不得不學習這些。

當你在解釋器中玩耍時,事情會變得混亂,因爲在那裏你傾向於將價值綁定在任何環境中。在口譯員GHCi必須自己執行let c = 20,因爲你還沒有輸入3 * c/4。它沒有辦法知道你是否打算是20是一個IntIntegerFloatDoubleRational

哈斯克爾將挑選數值的默認類型;否則,如果你從來沒有使用任何函數只適用於一個特定類型的數字,你會總是得到一個關於不明確的類型變量的錯誤。這通常工作正常,因爲這些默認規則是在讀取整個模塊時應用的,因此需要考慮類型上的所有其他約束(例如,您是否曾使用過/)。但是在這裏沒有其他的限制,所以類型默認選擇了第一個駕駛室,並且使得cInteger

然後,當您要求GHCi評估3 * c/4時,已爲時過晚。 cInteger,所以必須3 * c是,而Integer不支持/

所以在解釋器中,有的時候,如果你沒有給出明確的類型綁定GHC會選擇一個不正確的類型,尤其是數字類型。在那之後,你堅持被GHCi選擇的具體類型支持的任何操作,但是當你遇到這種錯誤時,你總是可以重新綁定變量;例如let c = 20.0

但是我懷疑在你的真實程序中,問題只不過是你想要的操作實際上是div而不是/

0

(/)的類型是分數a => a - > a - > a。

要分割整數,請使用div而不是(/)。注意,div類型是

div :: Integral a => a -> a -> a 

在最頂層語言編譯器適用於我 - 不是周圍的其他方式。

我認爲Haskell編譯器對於您的作用與您使用的其他語言的作用相同,甚至更多。 Haskell與傳統的命令式語言(例如C,C++,Java等)不同,它可能是您使用過的不同的語言。這意味着編譯器的工作方式也不同。

正如其他人所說,Haskell將從不自動從一種類型強制到另一種。如果您需要將一個整數用作Float,則需要使用fromInteger明確進行轉換。