You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Cabal项目中合理组织公共与私有库?

Haskell多模块项目目录组织方案探讨

项目背景与目标

我有一个多模块Haskell项目,情况如下:

  • 2个对外导出的公共库模块:A和B(B是后端模块,被A调用,但因其他项目可能用到B,所以选择同时导出两者);
  • 其余为内部模块;
  • 还有一个包含main :: IO ()的Main.hs,对应executable stanza,目前用于验证B的导出实体,后续会替换为测试代码。

希望实现以下目标:

  • 每个文件对应一个模块,且每个模块对应独立的library stanza;
  • 每个library的build-depends仅包含该模块import语句所需的依赖,数量最少;
  • 导入模块时,import Foo.Bar.Baz格式无重复内容。

当前尝试的目录结构

最初的方案是给每个模块设置独立目录、独立的library stanza和build-depends列表。目前尝试的结构如下:

.
|
+--- lib/
|    |
|    +--- A/
|    |    |
|    |    +--- A.hs
|    |    |
|    |    +--- Internal/
|    |               |
|    |               +--- E.hs
|    +--- B/
|    |    |
|    |    +--- B.hs
|    |
|    +--- C/
|    |    |
|    |    +--- C.hs
|    |
|    +--- D/
|    |    |
|    |    +--- D.hs
|
+--- exe/
|    |
|    +--- Main.hs
|
+--- myproject.cabal

结构说明

  • E.hs是A的实现细节,放在A/Internal目录下,导入方式为:
    import A.Internal.E
    
  • 公共模块A和B的导入方式符合定位:
    import A
    import B
    
  • 若C是B的实现细节,调整结构如下,此时每个模块仍有独立目录,可各自配置build-depends:
    .
    |
    +--- lib/
    |    |
    |    +--- A/
    |    |    |
    |    |    +--- A.hs
    |    |    |
    |    |    +--- Internal/
    |    |               |
    |    |               +--- E.hs
    |    +--- B/
    |    |    |
    |    |    +--- B.hs
    |    |    |
    |    |    +--- Internal/
    |    |               |
    |    |               +--- C.hs
    |    +--- D/
    |         |
    |         +--- D.hs
    |
    +--- exe/
    |    |
    |    +--- Main.hs
    |
    +--- myproject.cabal
    

遇到的困境

当D也作为A的实现细节时,若把D.hs放在A/Internal目录下:

.
|
+--- lib/
|    |
|    +--- A/
|    |    |
|    |    +--- A.hs
|    |    |
|    |    +--- Internal/
|    |               |
|    |               +--- E.hs
|    |               |
|    |               +--- D.hs
...

此时E.hs和D.hs处于同一目录,无法为它们设置独立的library stanza及各自的build-depends。

若为每个内部模块添加一层目录:

.
|
+--- lib/
|    |
|    +--- A/
|    |    |
|    |    +--- A.hs
|    |    |
|    |    +--- Internal/
|    |               |
|    |               +--- E/
|    |               |    |
|    |               |    +--- E.hs
|    |               |
|    |               +--- D/
|    |                    |
|    |                    +--- D.hs
...

会导致导入时出现重复命名:

import A.Internal.D.D
import A.Internal.E.E

这种写法不够简洁美观。

可行的目录组织方案

方案1:利用cabal配置拆分library

不需要给每个内部模块单独建目录,而是通过cabal的hs-source-dirs和exposed-modules为每个模块创建独立的library stanza,精准指定源文件和依赖。

例如,在myproject.cabal中配置:

library a-internal-e
  hs-source-dirs: lib/A/Internal
  exposed-modules: A.Internal.E
  build-depends: base >=4.14 && <5
  -- 仅添加E.hs所需的依赖

library a-internal-d
  hs-source-dirs: lib/A/Internal
  exposed-modules: A.Internal.D
  build-depends: base >=4.14 && <5
  -- 仅添加D.hs所需的依赖

library A
  hs-source-dirs: lib/A
  exposed-modules: A
  build-depends: base >=4.14 && <5, a-internal-e, a-internal-d

这种方式下,E.hs和D.hs仍在A/Internal目录下,导入路径保持import A.Internal.E和import A.Internal.D,无重复内容,同时每个模块拥有独立的依赖配置。

方案2:扁平化内部模块目录,调整命名前缀

将所有内部模块放在统一的internal目录下,通过模块名前缀区分所属公共库,目录结构如下:

.
|
+--- lib/
|    |
|    +--- A/
|    |    |
|    |    +--- A.hs
|    +--- B/
|    |    |
|    |    +--- B.hs
|    +--- internal/
|    |    |
|    |    +--- A_E.hs
|    |    |
|    |    +--- A_D.hs
|    |    |
|    |    +--- B_C.hs
|
+--- exe/
|    |
|    +--- Main.hs
|
+--- myproject.cabal

对应的模块名设为Internal.A.E、Internal.A.D、Internal.B.C,导入时写:

import Internal.A.E
import Internal.A.D

每个内部模块仍可配置独立的library stanza,按需设置build-depends,同时避免了导入路径重复。

方案3:独立子项目拆分

将每个模块(包括公共库和内部模块)作为独立子项目,每个子项目有自己的.cabal文件,目录结构如下:

.
|
+--- A/
|    |
|    +--- src/
|    |    |
|    |    +--- A.hs
|    |
|    +--- A.cabal
|
+--- A-Internal-E/
|    |
|    +--- src/
|    |    |
|    |    +--- A/
|    |    |    |
|    |    |    +--- Internal/
|    |    |               |
|    |    |               +--- E.hs
|    |
|    +--- A-Internal-E.cabal
|
+--- A-Internal-D/
|    |
|    +--- src/
|    |    |
|    |    +--- A/
|    |    |    |
|    |    |    +--- Internal/
|    |    |               |
|    |    |               +--- D.hs
|    |
|    +--- A-Internal-D.cabal
|
+--- B/
|    |
|    +--- src/
|    |    |
|    |    +--- B.hs
|    |
|    +--- B.cabal
|
+--- B-Internal-C/
|    |
|    +--- src/
|    |    |
|    |    +--- B/
|    |    |    |
|    |    |    +--- Internal/
|    |    |               |
|    |    |               +--- C.hs
|    |
|    +--- B-Internal-C.cabal
|
+--- exe/
|    |
|    +--- Main.hs
|    |
|    +--- exe.cabal
|
+--- cabal.project

在cabal.project中声明所有子项目:

packages:
  ./A
  ./A-Internal-E
  ./A-Internal-D
  ./B
  ./B-Internal-C
  ./exe

这种方式下每个模块完全独立,build-depends可精准控制,导入路径无重复,但目录结构更分散,适合模块独立性极强的场景。


内容的提问来源于stack exchange,提问作者Enlico

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 12:15:57