多亏了 newtype
和 GeneralizedNewtypeDeriving
扩展,人们可以毫不费力地定义不同的轻量级类型:
newtype PersonId = PersonId Int deriving (Eq, Ord, Show, NFData, ...)
newtype GroupId = GroupId Int deriving (Eq, Ord, Show, NFData, ...)
这允许类型系统确保
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
这是类型安全的,因为 map 是由键类型参数化的,而且
Set.member
操作是多态的,所以我不需要定义每个 id 类型的变体,例如 personIdSetMember
和 groupIdSetMember
(以及所有其他设置操作我可能想使用)...但是我怎样才能以与上述示例类似的方式分别使用更高效的
IntSet
s 来代替 PersonIdSet
和 GroupIdSet
呢?是否有一种无需将整个 Data.IntSet API 包装/复制为类型类的简单方法? 最佳答案
我认为你必须像你说的那样包装 IntSet
。然而,不是分别定义每个 ID 类型,您可以引入一个幻像类型来创建一系列相互兼容的 ID
和 IDSet
:
{-# 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
关于haskell - 如何 "newtype"IntSet?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/5746590/