2011-04-21 28 views
2

由於newtypeGeneralizedNewtypeDeriving擴展,可以定義不同的輕巧類型很少的努力:如何「newtype」IntSet?

newtype PersonId = PersonId Int deriving (Eq, Ord, Show, NFData, ...) 
newtype GroupId = GroupId Int deriving (Eq, Ord, Show, NFData, ...) 

允許類型系統,以確保一個PersonId不使用其中一個GroupId預期的事故,但仍然從Int繼承選定的類型實例。

現在,人們可以簡單地定義PersonIdSetGroupIdSet作爲

import Data.Set (Set) 
import qualified Data.Set as Set 

type PersonIdSet = Set PersonId 
type GroupIdSet = Set GroupId 

noGroups :: GroupIdSet 
noGroups = Set.empty 

-- should not type-check 
foo = PersonId 123 `Set.member` noGroups 

-- should type-check 
bar = GroupId 123 `Set.member` noGroups 

它的類型是安全的,因爲地圖是通過鑰匙式參數化,而且,在Set.member操作是多態的,所以我並不需要定義每個ID型變體,如personIdSetMembergroupIdSetMember(和所有其他集合的操作我可能要使用)

...但我該如何使用更有效的IntSet!而非爲PersonIdSetGroupIdSet分別與上面的例子類似嗎?是否有一種簡單的方法,無需將整個Data.IntSet API作爲類型類包裝/複製?

+0

IIRC no。但我想你可以使用模板Haskell。 – fuz 2011-04-21 16:04:56

+0

你寫了評論說什麼應該輸入檢查,什麼不是。您是否嘗試編譯,結果與您預期的結果不同? – jmg 2011-04-21 16:05:59

+0

@jmg,我只是試圖確定,結果如預期。 GHC發出的實際錯誤是'無法與實際類型'GroupId''匹配的預期類型'PersonId' – hvr 2011-04-21 19:53:25

回答

6

我想你必須按照你所說的來包裝IntSet。然而,而不是單獨定義每個ID類型,您可以引入一個幻象類型來創建一個家庭的ID S和IDSet S中的相互兼容:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

import qualified Data.IntSet as IntSet 
import Data.IntSet (IntSet) 

newtype ID a = ID { unID :: Int } 
       deriving (Eq, Ord, Show, Num) 

newtype IDSet a = IDSet { unIDSet :: IntSet } 
       deriving (Eq, Ord, Show) 

null :: IDSet a -> Bool 
null = IntSet.null . unIDSet 

member :: ID a -> IDSet a -> Bool 
member i = IntSet.member (unID i) . unIDSet 

empty :: IDSet a 
empty = IDSet $ IntSet.empty 

singleton :: ID a -> IDSet a 
singleton = IDSet . IntSet.singleton . unID 

insert :: ID a -> IDSet a -> IDSet a 
insert i = IDSet . IntSet.insert (unID i) . unIDSet 

delete :: ID a -> IDSet a -> IDSet a 
delete i = IDSet . IntSet.delete (unID i) . unIDSet 

因此,假設你有一個Person類型,一個Group類型,你可以這樣做:

type PersonID = ID Person 
type PersonIDSet = IDSet Person 

type GroupID = ID Group 
type GroupIDSet = IDSet Group 
+0

看起來很有趣,我想知道這個包裝是否可以在TH的幫助下自動化(以及如何),如FUZxxl建議的 – hvr 2011-04-21 20:03:20

-1

我覺得您認爲使用type而不是newtype的效率較低。這是不正確的,newtype通常比data更有效地執行。

因此,您的PersonIdSet的定義是完全安全和高效的,你可能想要的。

+3

我指的是低效率是關於'IntSet' vs'Set' – hvr 2011-04-21 19:49:33

1

enummapset包實現了一個方法來newtype -safe IntMap/IntSet秒。

基於從原始問題的類型對於其使用的一個例子:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

import   Data.EnumSet (EnumSet) 
import qualified Data.EnumSet as ES 

newtype PersonId = PersonId Int deriving Enum 
newtype GroupId = GroupId Int deriving Enum 

type PersonIdSet = EnumSet PersonId 
type GroupIdSet = EnumSet GroupId 

noGroups :: GroupIdSet 
noGroups = ES.empty 

-- fails type-check: Couldn't match expected type `PersonId' with actual type `GroupId' 
foo = PersonId 123 `ES.member` noGroups 

-- passes type-check 
bar = GroupId 123 `ES.member` noGroups 

Data.EnumMap用法類似。