2013-05-03 58 views
6

我是Haskell的新手,對於類型的工作方式有點困惑。下面是什麼我試圖做一個簡單的例子:Haskell類型類應該如何使用類型?

data ListOfInts = ListOfInts {value :: [Int]} 
data ListOfDoubles = ListOfDoubles {value :: [Double]} 

class Incrementable a where 
    increment :: a -> a 

instance Incrementable ListOfInts where 
    increment ints = map (\x -> x + 1) ints 

instance Incrementable ListOfDoubles where 
    increment doubles = map (\x -> x + 1) doubles 

(我意識到遞增列表中的每個元素可以非常簡單地完成,但是這是一個比較複雜的問題,只是一個簡化版本)

編譯器告訴我,我有多個聲明value。如果我改變的ListOfIntsListOfDoubles的定義如下:

type ListOfInts = [Int] 
type ListOfDoubles = [Double] 

那麼編譯器說(和同樣爲ListOfDoubles「的‘遞增的ListOfInts’非法實例聲明:」如果我用NEWTYPE,例如,newtype ListOfInts = ListOfInts [Int],那麼編譯器告訴我「無法與實際類型'[b0]'」匹配預期類型'ListOfInts'(並且類似於ListOfDoubles

我對類型類的理解是它們有助於多態性,但我顯然缺少某些東西。在上面的第一個例子中,編譯器是否看到類型參數a引用了一個記錄rd的字段名爲value,看來我試圖以多種方式爲此類型定義increment(而不是看到兩種不同的類型,一種字段的類型爲Int s的列表,而另一種類型是一個列表Double s)?對於其他嘗試類似嗎?

在此先感謝。

+1

地圖期望一個列表,你給它ListOfInts – Arjan 2013-05-03 09:22:04

回答

17

你真的看到兩個不同的問題,所以我會像這樣解決它們。

第一個是value字段。 Haskell記錄的工作方式有點奇怪:當您命名一個字段時,它會自動添加到當前作用域中作爲函數。從本質上講,你能想到的

data ListOfInts = ListOfInts {value :: [Int]} 

的語法糖:

data ListOfInts = ListOfInts [Int] 

value :: ListOfInt -> [Int] 
value (ListOfInts v) = v 

因此具有相同的字段名稱記錄就像是具有相同名稱的兩個不同的功能 - 他們交疊。這就是爲什麼你的第一個錯誤告訴你,你已經多次聲明values

來解決,這將是使用記錄語法定義你的類型沒有,因爲我做上面的方法:

data ListOfInts = ListOfInts [Int] 
data ListOfDoubles = ListOfDoubles [Double] 

當你使用type代替data,您只需創建一個類型的同義詞,而比一個新的類型。使用

type ListOfInts = [Int] 

意味着ListOfInts是一樣的只是[Int]。由於各種原因,默認情況下,不能在類實例中使用類型同義詞。這很有道理 - 嘗試寫一個[Int]的實例和一個ListOfInts會導致錯誤,這很容易出錯。

使用data來包裝單個類型,如[Int][Double]與使用newtype相同。然而,newtype的優點是它根本沒有運行時間開銷。所以寫這些類型的最好辦法確實是與newtype

newtype ListOfInts = ListOfInts [Int] 
newtype ListOfDoubles = ListOfDoubles [Double] 

要注意的重要一點是,當你使用datanewtype,你也有,如果你想在爲「解包」類型其內容。您可以使用模式匹配做到這一點:

instance Incrementable ListOfInts where 
    increment (ListOfInts ls) = ListOfInts (map (\ x -> x + 1) ls) 

這解開了ListOfInts,映射在它的內容的功能和包裝它回來了。

只要你用這種方式打開這個值,你的實例就可以工作。

在附註上,您可以使用稱爲「操作員部分」的東西,將map (\ x -> x + 1)編寫爲map (+ 1)。所有這一切意味着你隱式地創建一個lambda填充,無論操作符的哪個參數丟失。大多數人認爲map (+ 1)版本易於閱讀,因爲不必要的噪音較少。

+0

非常感謝,Tikhon,這很清楚。 – Chris 2013-05-03 09:54:23