理解`GHC.TypeLits`

Understanding `GHC.TypeLits`

我正在努力思考 GHC 扩展 KindSignaturesDataKinds。看Data.Modular这个包,大概明白了

newtype i `Mod` (n :: Nat) = Mod i deriving (Eq, Ord)

有点等同于声明一个 c++ 模板 <typename T, int N>(构造函数只采用一个 T 类型的参数)。但是,查看 GHC.TypeLits 包,我不明白发生了什么。关于这个包的任何一般解释都会有所帮助。不过,在这个问题被标记为题外话之前,这里有一些具体的子问题:

这个问题很宽泛 -- 我只谈几点。

proxy类型变量只是* -> *类型的类型变量,即类型构造函数的种类。务实地说,如果你有一个函数

foo :: proxy a -> ...

您可以将类型的值传递给它,例如Maybe Int,选择 proxy = Maybea = Int。您还可以传递 [] Char 类型的值(也写为 [Char])。或者,更常见的是 Proxy Int 类型的值,其中 Proxy 是定义为

的数据类型
data Proxy a = Proxy

即一种不携带任何 运行 时间信息的数据类型(它只有一个值!),但携带编译时信息(phantom 类型变量 a).

假设 N 是一种类型 Nat -- 编译时自然。我们可以写一个函数

bar :: N -> ...

但是调用它需要我们构建一个 N 类型的值——这是无关紧要的。 type N 的目的只是为了携带编译时信息,而它的 运行-time 值并不是我们真正想要使用的东西。事实上,N 除了 bottom 之外根本没有任何值。我们可以调用

bar (undefined :: N)

但这看起来很奇怪。读到这里,我们必须意识到 bar 在它的第一个参数中是惰性的,并且它不会导致尝试使用它的分歧。问题在于 bar :: N -> ... 类型签名具有误导性:它声称结果可能取决于类型 N 的参数值,但事实并非如此。相反,如果我们使用

baz :: Proxy N -> ...

意图很明确——只有一个 运行 时间值:Proxy :: Proxy N。同样清楚的是 N 值仅在编译时出现。

有时,不是使用特定的 Proxy N,而是将代码稍微概括为

foo :: proxy N -> ...

实现相同的目标,但也允许不同的 Proxy 类型。 (就个人而言,我对这种概括并不感到非常兴奋。)

回到问题:natVal 是一个将编译时纯自然值转换为 运行 时值的函数。 IE。它将 Proxy N 转换为 Int,仅返回常量。

如果您使用 type 模板参数来模拟编译时自然值,您与 C++ 模板的类比可能会更接近。例如

template <typename N> struct S { using pred = N; };
struct Z {};

template <typename N> int natVal();
template <typename N> int natVal() { return 1 + natVal<typename N::pred>(); }
template <>           int natVal<Z>() { return 0; }

int main() {
    cout << natVal<S<S<Z>>>() << endl;  // outputs 2
    return 0;
}

假装 SZ 没有 public 构造函数:它们的 运行 时间值并不重要,只有它们的编译时信息才重要。