我在hackage上看到了一些包含.Internal
作为其姓氏组件的模块名称的包(例如Data.ByteString.Internal
)
这些模块通常不能正常浏览(但它们可能会出现在Haddock中),不应该被客户端代码使用,但包含从暴露模块重新导出或仅在内部使用的定义。
现在我对这个图书馆组织模式的问题是:
.Internal
模块解决了哪些问题?.Internal
个模块?.Internal
模块的帮助下组织图书馆的做法是什么?答案 0 :(得分:24)
Internal
模块通常是公开包的 internals 的模块,它打破包封装。
以ByteString
为例:当您正常使用ByteString
时,它们将用作不透明数据类型; ByteString
值是原子的,它的表示是无趣的。 Data.ByteString
中的所有函数都取值ByteString
,而不是原始Ptr CChar
或其他。
这是好事;这意味着ByteString
作者设法使表示抽象得足以使ByteString
的所有细节都可以完全隐藏在用户之外。这样的设计导致了封装的功能。
Internal
模块适用于希望使用封装概念内部的人员,以扩展封装。
例如,您可能希望制作新的BitString
数据类型,并希望用户能够将ByteString
转换为BitString
而无需复制任何内存。为此,您不能使用opaque ByteString
,因为这不允许您访问代表ByteString
的内存。您需要访问指向字节数据的原始内存指针。这就是Internal
提供的ByteString
模块。
然后,您应该将BitString
数据类型封装起来,从而扩展封装而不会破坏它。然后,您可以自由地提供自己的BitString.Internal
模块,为可能希望依次检查其表示的用户公开数据类型的内部。
如果某人未提供Internal
模块(或类似模块),您将无法访问模块的内部表示,并且用户可以通过BitString
被迫(ab)使用诸如unsafeCoerce
之类的内容来投射内存指针,事情变得丑陋。
应放在Internal
模块中的定义是数据类型的实际数据声明:
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
答案 1 :(得分:18)
@dflemstr是对的,但不明确以下几点。一些作者将包的内部放在.Internal
模块中,然后不通过cabal暴露该模块,从而使客户端代码无法访问。 这是一件坏事 1 。
Exposed .Internal
模块有助于传达模块实现的不同抽象级别。替代方案是:
(1)使文档混乱,并使用户难以告诉他的代码之间的转换,尊重模块的抽象和破坏它。这种转变很重要:它类似于将参数移除到函数并用常量替换它的出现,失去了一般性。
(2)使上述过渡不可能和hinders the reuse of code。我们希望尽可能使代码尽可能抽象,但(参见爱因斯坦)不再如此,并且模块作者没有模块用户那么多的信息,所以无法确定应该是什么代码< EM>不可访问。有关此论点的更多信息,请参阅此链接,因为它有点特殊和有争议。
公开.Internal
模块提供了一种快乐的媒介,它可以在不强制执行的情况下传达抽象障碍,允许用户轻松地将自己限制为抽象代码,但允许他们在抽象出现故障时“扩展”模块的使用。是不完整的。
1 这种纯粹的判断当然有并发症。内部更改现在可以破坏客户端代码,作者现在有更大的义务来稳定他们的实现以及他们的接口。即使它被正确放弃,用户也是用户并得到支持,因此隐藏内部结构有一些吸引力。它需要一个自定义版本策略来区分.Internal
和接口更改,但幸运的是,这与versioning policy一致(但不明确)。 “真正的代码”也是出了名的懒惰,所以当有一种抽象的方法来定义“更难”的代码(但最终支持社区的重用)时,暴露.Internal
模块可以提供一个简单的方法。它还可以阻止在抽象界面中报告真正应该推送给作者修复的遗漏。
答案 2 :(得分:6)
我们的想法是,您可以拥有从MyModule
导出的“正确”,稳定的API,这是使用该库的首选和记录方式。
除了公共API之外,您的模块可能还有私有数据构造函数和内部帮助函数等。MyModule.Internal
子模块可用于导出这些内部函数,而不是将它们完全锁定在模块中。
答案 3 :(得分:2)
shang和dflemstr所说的一个扩展(或可能澄清):如果你有内部定义(不导出构造函数的数据类型等),你想要从 < em> export,然后你通常会创建一个完全没有公开的.Internal
模块(即列在Other-Modules
文件中的.cabal
)。
然而,当在ghci中执行类型时,有时会泄漏(例如,当使用函数但其引用的某些类型不在范围内时;无法想到实例这发生在我的头顶,但确实如此。