【问题标题】:How to "newtype" IntSet?如何“newtype” IntSet?
【发布时间】:2011-08-02 06:46:58
【问题描述】:

感谢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 操作是多态的,所以我不需要定义每个 id 类型的变体,例如 personIdSetMember 和 @987654332 @(以及我可能想要使用的所有其他集合操作)

...但是如何以与上述示例类似的方式分别使用更有效的IntSets 来代替PersonIdSetGroupIdSet?有没有一种简单的方法,无需将整个 Data.IntSet API 包装/复制为类型类?

【问题讨论】:

  • IIRC 编号但我想你可以使用模板 Haskell。
  • 你写了 cmets 说什么应该输入检查,什么不应该。你有没有试过编译,哪里的结果和你预期的不一样?
  • @jmg,我只是想确定一下,结果符合预期。 GHC 发出的实际错误是Couldn't match expected type 'PersonId' with actual type 'GroupId'
  • 那么,您问题中的代码提供了您要询问的所有属性,不是吗?还是你想要别的?
  • @jmg: 是的,但我希望这些属性具有更高效的IntSet 而不是通用的Set

标签: haskell types


【解决方案1】:

我认为你必须像你说的那样包装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

所以,假设你有一个Person 类型和一个Group 类型,你可以这样做:

type PersonID = ID Person
type PersonIDSet = IDSet Person

type GroupID = ID Group
type GroupIDSet = IDSet Group

【讨论】:

  • 看起来很有趣,我想知道这种包装是否(以及如何)可以在 TH 的帮助下实现自动化,正如 FUZxxl 所建议的那样
【解决方案2】:

enummapset 包实现了newtype-safe IntMap/IntSets 的一种方法。

基于原始问题类型的用法示例:

{-# 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的用法类似。

【讨论】:

    【解决方案3】:

    我的印象是,您认为使用type 而不是newtype 效率较低。这不是真的,newtypes 通常比datas 更有效地实现。

    因此,您对PersonIdSet 的定义是完全安全且高效的。

    【讨论】:

    • 我指的效率低下大约是IntSet vs Set
    猜你喜欢
    • 2012-02-20
    • 2010-09-05
    • 2022-10-12
    • 2011-04-04
    • 1970-01-01
    • 2015-07-21
    • 1970-01-01
    • 2014-10-25
    • 1970-01-01
    相关资源
    最近更新 更多