[英]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
是数字,或者aN
是N
,数字是N
这是因为您经常需要“刷新”类型变量才能完成推理,因此您需要一种快速创建新名称的方法。 为此,使用编号变量非常简单。
推理完成时显示的名称可以“漂亮打印”。 推理器可以重命名变量以使用a
, b
, c
等代替_1
, _2
等。
诀窍是大多数操作都有明确的类型签名。 某些定义需要量化某些类型变量( instance
, class
, data
和instance
)。 用户明确提供的所有这些名称都可用于以更好的方式显示类型。
在推理时,您可以以某种方式跟踪新鲜类型变量的来源,以便能够在向用户显示时更加明智地重命名它们。 另一种选择是通过向它们添加数字来刷新变量。 例如,一个新类型的return
可能是Monad m0 => a0 -> m0 a0
(这里我们知道使用m
和a
只是因为Monad
的class
定义使用这些名称)。 推理完成后,你可以摆脱数字并获得漂亮的名字。
通常,推理器将尝试使用通过签名明确提供的名称。 如果已经使用了这样的名称,它可能决定添加一个数字而不是使用不同的名称(例如,如果b
已被绑定,则使用b1
而不是c
)。
可能还有其他一些临时规则。 例如,元组元素具有t
, t1
, t2
, t3
等的事实可能是通过自定义规则完成的。 事实上, 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,在示例中选择a
或t
无关紧要。 选择类型变量名称以最大化人类的便利是一个完全不同的主题,可能要复杂得多!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.