繁体   English   中英

如何在GHC推断出的类型签名中选择变量名?

[英]How are variable names chosen in type signatures inferred by GHC?

当我使用:t检查Haskell中的函数类型时,例如我之前的问题中的那些,我倾向于得到如下结果:

Eq a => a -> [a] -> Bool
(Ord a, Num a, Ord a1, Num a1) => a -> a1 -> a
(Num t2, Num t1, Num t, Enum t2, Enum t1, Enum t) =>  [(t, t1, t2)]

看起来这不是一个微不足道的问题 - Haskell解释器如何选择文字来象征类型类? 什么时候会选择a ,而不是t 什么时候会选择a1而不是b 程序员的观点是否重要?

类型变量的名称并不重要。 方式:

Eq element => element -> [element] -> Bool

完全相同:

Eq a => a -> [a] -> Bool

有些名称更容易阅读/记忆。

现在,推理器如何为类型选择最佳名称?

免责声明:我绝对不是 GHC开发人员。 但是我正在为我的学士论文中的Haskell开发一个类型推理器。

在推理期间,为变量选择的名称可能不是那么可读。 事实上,它们几乎肯定是_N的行,其中N是数字,或者aNN ,数字是N

这是因为您经常需要“刷新”类型变量才能完成推理,因此您需要一种快速创建新名称的方法。 为此,使用编号变量非常简单。

推理完成时显示的名称可以“漂亮打印”。 推理器可以重命名变量以使用abc等代替_1_2等。

诀窍是大多数操作都有明确的类型签名。 某些定义需要量化某些类型变量( instanceclassdatainstance )。 用户明确提供的所有这些名称都可用于以更好的方式显示类型。

在推理时,您可以以某种方式跟踪新鲜类型变量的来源,以便能够在向用户显示时更加明智地重命名它们。 另一种选择是通过向它们添加数字来刷新变量。 例如,一个新类型的return可能是Monad m0 => a0 -> m0 a0 (这里我们知道使用ma只是因为Monadclass定义使用这些名称)。 推理完成后,你可以摆脱数字并获得漂亮的名字。

通常,推理器将尝试使用通过签名明确提供的名称。 如果已经使用了这样的名称,它可能决定添加一个数字而不是使用不同的名称(例如,如果b已被绑定,则使用b1而不是c )。

可能还有其他一些临时规则。 例如,元组元素具有tt1t2t3等的事实可能是通过自定义规则完成的。 事实上, t不会出现在(,,)的签名中。

GHCi如何选择类型变量的名称? 解释了这些变量名称的数量。 正如Ganesh Sittampalam在评论中指出的那样,算术序列似乎发生了一些奇怪的事情。 Haskell 98报告和Haskell 2010报告都表明了这一点

[e1..] = enumFrom e1

但是,GHCi给出了以下内容:

Prelude> :t [undefined..]
[undefined..] :: Enum t => [t]

Prelude> :t enumFrom undefined
enumFrom undefined :: Enum a => [a]

这清楚地表明奇怪的行为与Enum类本身无关,而是从将句法序列翻译成enumFrom形式的某个阶段进入。 我想知道GHC是不是真的没有使用那个翻译,但它确实是:

{-# LANGUAGE NoMonomorphismRestriction #-}
module X (aoeu,htns) where
aoeu = [undefined..]
htns = enumFrom undefined

使用ghc -ddump-simpl enumlit.hs编译的ghc -ddump-simpl enumlit.hs给出

X.htns :: forall a_aiD. GHC.Enum.Enum a_aiD => [a_aiD]
[GblId, Arity=1]
X.htns =
  \ (@ a_aiG) ($dEnum_aiH :: GHC.Enum.Enum a_aiG) ->
    GHC.Enum.enumFrom @ a_aiG $dEnum_aiH (GHC.Err.undefined @ a_aiG)

X.aoeu :: forall t_aiS. GHC.Enum.Enum t_aiS => [t_aiS]
[GblId, Arity=1]
X.aoeu =
  \ (@ t_aiV) ($dEnum_aiW :: GHC.Enum.Enum t_aiV) ->
    GHC.Enum.enumFrom @ t_aiV $dEnum_aiW (GHC.Err.undefined @ t_aiV)

所以这两个表示之间的唯一区别是指定的类型变量名称。 我不知道有足够的了解GHC工作如何知道那t从何而来,但至少我已经收窄!


ØrjanJohansen在评论中指出,函数定义和lambda抽象似乎发生了类似的事情。

Prelude> :t \x -> x
\x -> x :: t -> t

Prelude> :t map (\x->x) $ undefined
map (\x->x) $ undefined :: [b]

在后一种情况下,类型b来自给予map的显式类型签名。

您是否熟悉alpha等效alpha替换的概念? 这捕获了这样的概念,例如,即使它们不同,以下两者都是完全等效和可互换的(在某些情况下):

\x -> (x, x)
\y -> (y, y)

相同的概念可以扩展到类型和类型变量的级别(有关进一步阅读,请参阅“系统F”)。 事实上,Haskell对于绑定类型变量有一个“类型级别的lambdas”的概念,但是很难看到,因为它们默认是隐式的。 但是,您可以通过使用ExplicitForAll扩展来使它们显ExplicitForAll ,并使用显式绑定您的类型变量:

ghci> :set -XExplicitForAll 
ghci> let f x = x; f :: forall a. a -> a

在第二行中,我使用forall关键字引入一个新的类型变量,然后在一个类型中使用。

换句话说,只要类型表达式满足alpha-equivalence,在示例中选择at无关紧要。 选择类型变量名称以最大化人类的便利是一个完全不同的主题,可能要复杂得多!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM