如何,为什么以及何时使用“.Internal”模块模式?

时间:2012-02-08 09:31:57

标签: haskell

我在hackage上看到了一些包含.Internal作为其姓氏组件的模块名称的包(例如Data.ByteString.Internal

这些模块通常不能正常浏览(但它们可能会出现在Haddock中),不应该被客户端代码使用,但包含从暴露模块重新导出或仅在内部使用的定义。

现在我对这个图书馆组织模式的问题是:

  • 那些.Internal模块解决了哪些问题?
  • 还有其他更好的方法可以解决这些问题吗?
  • 应将哪些定义移至.Internal个模块?
  • 目前推荐的有关在.Internal模块的帮助下组织图书馆的做法是什么?

4 个答案:

答案 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. 通过不在模块导出或通过cabal公开它们来隐藏实现细节。
  3. (1)使文档混乱,并使用户难以告诉他的代码之间的转换,尊重模块的抽象和破坏它。这种转变很重要:它类似于将参数移除到函数并用常量替换它的出现,失去了一般性。

    (2)使上述过渡不可能hinders the reuse of code。我们希望尽可能使代码尽可能抽象,但(参见爱因斯坦)不再如此,并且模块作者没有模块用户那么多的信息,所以无法确定应该是什么代码< EM>不可访问。有关此论点的更多信息,请参阅此链接,因为它有点特殊和有争议。

    公开.Internal模块提供了一种快乐的媒介,它可以在不强制执行的情况下传达抽象障碍,允许用户轻松地将自己限制为抽象代码,但允许他们在抽象出现故障时“扩展”模块的使用。是不完整的。


    1 这种纯粹的判断当然有并发症。内部更改现在可以破坏客户端代码,作者现在有更大的义务来稳定他们的实现以及他们的接口。即使它被正确放弃,用户也是用户并得到支持,因此隐藏内部结构有一些吸引力。它需要一个自定义版本策略来区分.Internal和接口更改,但幸运的是,这与versioning policy一致(但不明确)。 “真正的代码”也是出了名的懒惰,所以当有一种抽象的方法来定义“更难”的代码(但最终支持社区的重用)时,暴露.Internal模块可以提供一个简单的方法。它还可以阻止在抽象界面中报告真正应该推送给作者修复的遗漏。

答案 2 :(得分:6)

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

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

  • 它允许您的图书馆用户访问内部,如果他们有您没有预见到的需求,但理解他们正在访问内部API,而该内部API与公共API没有相同的隐含保证。
  • 它允许您访问内部函数和构造函数,例如单元测试目的。

答案 3 :(得分:2)

shang和dflemstr所说的一个扩展(或可能澄清):如果你有内部定义(不导出构造函数的数据类型等),你想要从 < em> export,然后你通常会创建一个完全没有公开的.Internal模块(即列在Other-Modules文件中的.cabal)。

然而,当在ghci中执行类型时,有时泄漏(例如,当使用函数但其引用的某些类型不在范围内时;无法想到实例这发生在我的头顶,但确实如此。