
Wednesday, October 7, 2026
20:23
写了一个 Nix 函数库的 DSL,模仿 Haskell 的模块系统。
模块内:
{ lib, use }:
let
inherit (use "data.nullable")
mapNullables
;
in
# ...
模块树根部:
{
pkgs ? import <nixpkgs> { },
lib ? pkgs.lib,
}:
import ./modular.nix {
inherit lib;
moduleDir = ./src;
}
Haskell 的怎么组织模块,这里就怎么组织,不同之处就是用 use 代替 import。如果想要 qualified import,可以直接写 nullable = use "data.nullable"。
PS:其实这个语法更像 Zig(笑
modular.nix 的实现如下:
{
lib,
moduleDir,
}:
let
evaluate = x: builtins.seq x x;
singletonAttrs = name: value: { ${name} = value; };
makeModuleTreeWithEnv =
env: dir:
let
dirEntries = builtins.readDir dir;
names = lib.lists.uniqueStrings (
lib.flip lib.lists.concatMap (lib.attrsets.attrsToList dirEntries) (
{ name, value }:
if value == "directory" then
[ name ]
else if value == "regular" && lib.strings.hasSuffix ".nix" name then
[ (lib.strings.removeSuffix ".nix" name) ]
else
[ ]
)
);
modules = lib.attrsets.listToAttrs (
lib.flip lib.lists.map names (name: {
inherit name;
value =
let
children = lib.attrsets.optionalAttrs (lib.attrsets.hasAttr name dirEntries) (
makeModuleTreeWithEnv env (dir + /${name}/.)
);
in
if lib.attrsets.hasAttr "${name}.nix" dirEntries then
rec {
inherit children;
path = dir + /${name}.nix;
content = import path env;
}
else
{ inherit children; };
})
);
in
modules;
makeModuleTree =
dir:
let
moduleTree = makeModuleTreeWithEnv moduleEnv dir;
moduleEnv = {
inherit lib;
use =
modulePath:
let
rawAttrPath = lib.strings.splitString "." modulePath;
attrPath = (lib.intersperse "children" rawAttrPath) ++ [ "content" ];
err = builtins.abort "module path \"${modulePath}\" doesn't exist";
in
evaluate (lib.attrsets.attrByPath attrPath err moduleTree);
};
evalModuleTree =
tree:
builtins.deepSeq (lib.flip lib.lists.map (lib.attrsets.attrValues tree) (
mod: builtins.seq (builtins.seq (mod.content or { }) (evalModuleTree mod.children)) 1
)) tree;
in
{
inherit (moduleEnv) use;
moduleTree = evalModuleTree moduleTree;
};
in
makeModuleTree moduleDir