Haskell 如何使用、为什么使用以及何时使用;。“内部”;模块模式?

Haskell 如何使用、为什么使用以及何时使用;。“内部”;模块模式?,haskell,Haskell,我见过几个包,其中包含模块名,其姓氏组件为.Internal(例如Data.ByteString.Internal) 这些模块通常不能在Haddock中正常浏览(但它们可能会显示),客户端代码不应使用这些模块,而是包含从公开模块重新导出或仅在内部使用的定义 现在,我对这个库组织模式的问题是: 那些内部模块解决了什么问题 有没有其他更好的方法来解决这些问题 哪些定义应该移动到那些内部模块 关于借助这样的内部模块组织库,当前推荐的做法是什么 我们的想法是,您可以拥有从MyModule导出的“适当的

我见过几个包,其中包含模块名,其姓氏组件为
.Internal
(例如
Data.ByteString.Internal

这些模块通常不能在Haddock中正常浏览(但它们可能会显示),客户端代码不应使用这些模块,而是包含从公开模块重新导出或仅在内部使用的定义

现在,我对这个库组织模式的问题是:

  • 那些
    内部模块解决了什么问题
  • 有没有其他更好的方法来解决这些问题
  • 哪些定义应该移动到那些
    内部
    模块
  • 关于借助这样的
    内部
    模块组织库,当前推荐的做法是什么

我们的想法是,您可以拥有从
MyModule
导出的“适当的”稳定API,这是使用库的首选和有文档记录的方式

除了公共API之外,您的模块可能还有私有数据构造函数和内部帮助函数等。
MyModule.internal
子模块可用于导出这些内部函数,而不是将它们完全锁定在模块内

  • 如果库中的用户有您没有预见到的需求,那么他们可以访问库中的内部API,但要理解他们正在访问的内部API与公共API没有相同的隐式保证
  • 它允许您访问内部函数和构造函数,例如用于单元测试

内部
模块通常是暴露包内部的模块,破坏包封装

ByteString
为例:当您通常使用
ByteString
s时,它们被用作不透明的数据类型;
ByteString
值是原子的,它的表示形式是无趣的。
Data.ByteString
中的所有函数都取
ByteString
的值,并且永远不要原始
Ptr CChar
s或其他东西

这是一件好事;这意味着
ByteString
作者设法使表示足够抽象,从而可以对用户完全隐藏
ByteString
的所有细节。这样的设计导致了功能的封装

内部
模块适用于希望使用封装概念内部的人员,以扩大封装范围

例如,您可能希望创建一个新的
位字符串
数据类型,并且您希望用户能够通过testring
位字符串转换为
位字符串
,而无需复制任何内存。为此,您不能使用不透明的
ByteString
s,因为这不允许您访问表示
ByteString
的内存。您需要访问指向字节数据的原始内存指针。这是
ByteString
s的
Internal
模块提供的功能

然后,还应封装
位字符串
数据类型,从而在不破坏封装的情况下扩展封装。然后,您可以自由提供自己的
BitString.Internal
模块,为可能希望依次检查其表示形式的用户公开数据类型的内部

如果有人没有提供
内部
模块(或类似模块),您将无法访问该模块的内部表示,而编写例如
位字符串
的用户将被迫(ab)使用
未安全性
之类的东西来投射内存指针,结果会变得很糟糕

应该放在
内部
模块中的定义是数据类型的实际数据声明:

module Bla.Internal where

data Bla = Blu Int | Bli String

-- ...

module Bla (Bla, makeBla) where -- ONLY export the Bla type, not the constructors

import Bla.Internal

makeBla :: String -> Bla -- Some function only dealing with the opaque type
makeBla = undefined
shang和dflemstr所说的一个扩展(或可能的澄清):如果您想从导出的多个模块访问内部定义(其构造函数不导出的数据类型等),那么您通常会创建这样一个根本不公开的
.internal
模块(即
.cabal
文件中的
其他模块中列出)


然而,当在ghci中执行类型时,这种情况有时确实会泄漏出来(例如,当使用函数时,它引用的某些类型不在范围内;我想不出有哪一个实例会突然发生这种情况,但它确实存在).

@dflemstr是正确的,但没有明确说明以下几点。一些作者将包的内部放入
.Internal
模块中,然后不通过cabal公开该模块,从而使客户端代码无法访问该模块。这是一件糟糕的事情1

Exposed
。内部
模块有助于传达模块实现的不同抽象级别。备选方案包括:

  • 在与抽象相同的模块中公开实现细节
  • 通过不在模块导出或通过cabal公开实现细节来隐藏它们
  • (1) 使文档变得混乱,用户很难分辨出代码在模块抽象和破坏模块抽象之间的转换。这种转换很重要:它类似于删除函数的一个参数,并用常量替换它的出现,失去了通用性

    (2) 使上述转换变得不可能,我们希望使代码尽可能抽象,但是(参见爱因斯坦)不再如此,模块作者没有模块用户那么多的信息,因此无法决定哪些代码应该不可访问。有关此论点的更多信息,请参阅链接,因为这有点奇怪和有争议

    Exposing
    .Internal
    模块提供了一个愉快的媒介,它可以在不强制实施抽象屏障的情况下传达抽象屏障,允许用户轻松地重新定义