【问题标题】:How does GHCi pick names for type variables?GHCi 如何为类型变量选择名称?
【发布时间】:2024-05-16 20:10:02
【问题描述】:

使用交互式 GHC 解释器时,可以询问表达式的推断类型:

Prelude> :t map
map :: (a -> b) -> [a] -> [b]

它似乎从签名中获取类型变量的名称,因为mapdefined

map :: (a -> b) -> [a] -> [b]
map _ []     = []
map f (x:xs) = f x : map f xs

在前奏曲中。这很有意义!我的问题是:在没有给出签名的情况下如何选择类型变量名称?

一个例子是

Prelude> :t map fst
map fst :: [(b, b1)] -> [b]

它选择了名称bb1。很明显,必须进行重命名,但只需从 ab、... 开始就可以了

map fst :: [(a, b)] -> [a]

相反,我觉得它更易读。

【问题讨论】:

    标签: haskell type-inference ghc ghci type-variables


    【解决方案1】:

    据我了解,ghci 选择名称的顺序与推断类型的顺序相同。它使用您提到的命名方案来决定结果的类型名称,即[b],因为这是在map 的定义中指定的类型名称。然后它决定作为map 的第一个参数的函数也应该返回b 类型的东西。

    因此,要命名的剩余类型变量是fst 的参数元组中第二个元素的类型变量,它再次查看fst 的定义来决定使用哪个名称。 fst :: (a, b) -> a 的定义,所以 b 将是这里的首选名称,但由于 b 已被占用,它附加了一个 1 使其变为 b1

    我认为这个系统在你不处理任意类型的情况下具有优势,就像这里的情况一样。如果生成的类型看起来像这样,例如:

    castAdd :: (Num n, Num n1, Num n2) => n -> n1 -> n2
    

    ... 可以说它比以下更具可读性:

    castAdd :: (Num a, Num b, Num c) => a -> b -> c
    

    ...因为您可以主要依赖 n# 表示数字类型,因为 Num 的类定义是 class Num n where ...

    编辑:是的,我知道castAdd 是不可能实现的,但这只是一个类型示例。

    【讨论】:

    • 谢谢,这是一个很好的解释!我没有想到您希望多个 n 重命名但保持相关的情况。
    • 这不是不可能的。 (unSafeCoerce 或只是普通的旧 _|_