如何“newtype”IntSet?

时间:2011-04-21 15:53:56

标签: haskell types

感谢newtypeGeneralizedNewtypeDeriving扩展程序,您可以轻松定义不同的轻量级类型:

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

这是类型安全的,因为map是按键类型参数化的,而且Set.member操作是多态的,所以我不需要定义per-id类型的变体,例如{{1} }和personIdSetMember(以及我可能想要使用的所有其他设置操作)

...但是,如何以与上述示例类似的方式分别为groupIdSetMemberIntSet使用效率更高的PersonIdSet?是否有一种简单的方法不必将整个Data.IntSet API包装/复制为类型类?

3 个答案:

答案 0 :(得分:7)

我认为你必须像你说的那样包裹IntSet。但是,您可以引入幻像类型来创建一个彼此兼容的IDIDSet系列,而不是单独定义每种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

因此,假设您有Person类型和Group类型,则可以执行以下操作:

type PersonID = ID Person
type PersonIDSet = IDSet Person

type GroupID = ID Group
type GroupIDSet = IDSet Group

答案 1 :(得分:3)

enummapset包实现了newtype的一种方法 - 安全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的用法类似。

答案 2 :(得分:-1)

我认为使用type代替newtype效率较低。事实并非如此,newtype通常比data更有效地实施。

因此,您对PersonIdSet的定义非常安全且效率高于您的预期。