感谢newtype和GeneralizedNewtypeDeriving扩展,可以轻松定义不同的轻量级类型:
newtype PersonId = PersonId Int deriving (Eq, Ord, Show, NFData, ...)
newtype GroupId = GroupId Int deriving (Eq, Ord, Show, NFData, ...)
Run Code Online (Sandbox Code Playgroud)
这允许类型系统确保a PersonId不会被意外使用GroupId,但仍然从中继承选定的类型类实例Int.
现在可以简单地定义PersonIdSet和GroupIdSet作为
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
Run Code Online (Sandbox Code Playgroud)
这是类型安全的,因为map是按键类型参数化的,而且,Set.member操作是多态的,所以我不需要定义per-id类型的变体,例如personIdSetMember和groupIdSetMember(以及我可能想要的所有其他set-operations)使用)
...但我怎么可以使用更高效IntSet!而非用于PersonIdSet和GroupIdSet分别在一个类似的方式,以上面的例子?是否有一种简单的方法不必将整个Data.IntSet API包装/复制为类型类?
我想你必须IntSet像你说的那样换行.但是,您可以引入幻像类型来创建彼此兼容的IDs和IDSets 族,而不是单独定义每种ID类型:
{-# 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
Run Code Online (Sandbox Code Playgroud)
因此,假设您有Person类型和Group类型,您可以执行以下操作:
type PersonID = ID Person
type PersonIDSet = IDSet Person
type GroupID = ID Group
type GroupIDSet = IDSet Group
Run Code Online (Sandbox Code Playgroud)