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
模块提供了一个愉快的媒介,它可以在不强制实施抽象屏障的情况下传达抽象屏障,允许用户轻松地重新定义