2014-01-14 93 views
5

下面的代碼無法編譯:Haskell的多態函數計算錯誤

foo :: Num a => (a -> a) -> Either Integer Double -> Either Integer Double 
foo f x = case x of 
    Left i -> Left $ f i 
    Right d -> Right $ f 

,並提供以下錯誤:

Couldn't match type `Integer' with `Double' 
Expected type: Either Integer Double 
Actual type: Either Integer a 
In the expression: Right $ f d 
In a case alternative: Right d -> Right $ f d 

這是一個後續問題this question,問題要解決使用RankNTypes:

(forall a. Num a => a -> a) 

但是答案並沒有說什麼。我想知道:

  • 這個錯誤的根本原因是什麼? 最終結果將只是其中一個案例分支,f不會同時鍵入兩種類型,f的類型應檢查長度爲f :: Num a => (a -> a),可以是整數 - >整數或雙精度 - >雙精度工作,有人可以詳細說明爲什麼這會導致錯誤?

  • 有沒有其他方法可以修復錯誤?爲什麼RankNTypes會修復錯誤?這讓我感覺像前一天得到的Monomorphism Restriction錯誤,但啓用它並不能幫助我解決這個問題,顯式類型註釋也不起作用。

+1

只是想象它會typecheck。你可以叫'foo(+ 1.5)(左5)'嗎?如果不是,爲什麼不呢?如果是的話,結果會是什麼? –

+1

除了類型的問題,你可以寫'foo f =(Left。f)(Right。f)'。 –

回答

6

其根本原因是,與您原來的定義,a是太籠統了。 考慮:

foo :: Num a => (a -> a) -> Either Integer Double -> Either Integer Double 
foo f x = case x of 
    Left i -> Left $ f i 

在這一點上,類型檢查器來麻煩,因爲Left $ f i類型必須是Either Integer Double,因此表達f i必須Integer。但是你說過,調用者可以傳遞任何映射數字類型的函數給它自己。例如,您的類型簽名允許通過Double -> Double函數。顯然,這樣的功能可能永遠不會導致Integer,因此f的應用在這裏沒有很好的輸入。

OTOH,如果您使用排名較高的類型的解決方案,則無法傳遞適用於特定類型的任何函數 - 僅適用於所有數字類型的函數。例如,您可以通過negate,但不通過((1::Integer)+)。這絕對是有道理的,因爲您也可以將相同的功能應用於另一種情況下的Double值。

所以,要回答你的第二個問題,考慮到你的代碼,排名較高的類型解決方案是正確的。如果您想將其應用於IntegerDouble,您顯然只能傳遞如negate這樣的函數。

底線:隨着

f :: (a -> a) -> b 

你可以傳遞功能,如idtailreverse((1::Int)+)。隨着

f :: (forall a. a -> a) -> b 

你只能通過功能與此類類型完全相同簽名forall a. a->a(模類型變量重命名)爲id,但上面提到的沒有其他人。

3

基本上這是一個範圍界定問題。讓我們比較一下下面的類型草案:

foo1 ::民A =>(A - > A) - > ...

foo2的::(FORALL一個民一=> A - >一) - > ...

在第一個聲明中,編譯器希望有一個類型爲a的實例爲Num,並且該類型的函數的類型爲a -> a。具體來說,只適用於Integer的函數將適用於此。當然,這樣的函數不適合你的任務,所以編譯器應該正確地拒絕你的給定類型簽名的實現。另一方面,第二種類型的簽名表示您需要類型a -> a的函數適用於全部Num的實例。與前者相比,這種類型顯着受限。

作爲一種變通方法,你可能需要的功能給予兩次:

foo :: (a -> a) -> (b -> b) -> Either a b -> Either a b 
foo f g = either (Left . f) (Right . g) 
1

它的範圍問題,真的。

在原文中,對於foo的每個實例,您都有一個新的類型變量。

foo :: forall a. Num a => (a -> a) -> Either Integer Double -> Either Integer Double 

a必須爲foo每個實例的整個主體相同的,並且只有一個Num實例是參與。而在rank-2多態版本中,對於f參數的每個實例,都有一個新的類型變量。

foo :: (forall a. Num a => a -> a) -> Either Integer Double -> Either Integer Double 

而且您正在討論儘可能多的Num實例,因爲有實例化。