我在一个使用两个IntMaps的文档中有一条记录:
data Doc = Doc { kernels :: IntMap Kernel, nodes :: IntMap Node }
但是我发现来自两个IntMaps的键具有不同的含义,并且我无法在两种不同类型中分离,并且在混合内核类型和节点类型时不会出错。我想要有从内核映射和节点映射检查密钥的函数,并且不允许混淆。 E.g:
someFunction :: Doc -> KernelKey -> NodeKey -> a
someFunction doc k1 k2 = .....
而不是当前:
someFunction :: Doc -> Int -> Int -> a
someFunction doc k1 k2 = .... -- warning check twice k1 and k2
它可以吗?或者我将从IntMap
更改为Map
。
由于
答案 0 :(得分:11)
您可以使用newtype
围绕Int
制作包装,以区分其含义。
newtype KernelKey = KernelKey Int
newtype NodeKey = NodeKey Int
someFunction :: Doc -> KernelKey -> NodeKey -> a
someFunction doc (KernelKey k1) (NodeKey k2) = ...
这样,您仍然可以在内部使用IntMap
,但会公开更安全的类型界面,尤其是如果您还控制KernelKey
和NodeKey
值的创建方式,即您不要导出它们的构造函数,因此用户只能将它们作为其他函数的返回值。
请注意newtype
包装器在运行时消失,因此这种额外的包装和解包不会以任何方式影响性能。
答案 1 :(得分:3)
您可以创建统一的密钥类型并为您的Doc类型包装IntMap API。它可能看起来像这样。
data DocValue = DocKernel Kernel
| DocNode Node
data DocKey = KernelKey Int
| NodeKey Int
docLookup :: DocKey -> Doc -> Maybe DocValue
该解决方案的优点在于您只需要每个所需的每个map API函数的一个副本。这是一个更接近你所拥有的代码的不同解决方案。
newtype NodeKey = NodeKey Int
newtype KernelKey = KernelKey Int
lookupDocNode :: NodeKey -> Doc -> Maybe Node
lookupDocKernel :: KernelKey -> Doc -> Maybe Kernel
您也可以在没有newtypes的情况下执行此解决方案。这两种解决方案都为您提供了类型安在第一个中,您必须使用类型指定所需的内容。在第二个中,您可以通过选择要调用的函数来指定它。