Haskell隐式参数和多态递归



我有一个问题"隐式参数和多态递归";GHC用户指南第章。

代码是

len1 :: [a] -> Int
len1 xs = let ?acc = 0 in len_acc1 xs
len_acc1 [] = ?acc
len_acc1 (_:xs) = let ?acc = ?acc + (1::Int) in len_acc1 xs
------------
len2 :: [a] -> Int
len2 xs = let ?acc = 0 in len_acc2 xs
len_acc2 :: (?acc :: Int) => [a] -> Int
len_acc2 [] = ?acc
len_acc2 (_:xs) = let ?acc = ?acc + (1::Int) in len_acc2 xs

这一章说

在前一种情况下,len_acc1在其右侧是单态的,那么隐式参数呢?acc不会传递给递归调用。在后一种情况下由于len_acc2具有类型签名,递归调用是对多态版本进行的?acc作为隐式参数。

问题是

  • 是否"在它自己的右手边是单态的";在这种情况下是指len_acc1 :: (?acc :: Int) => [a] -> p?为什么ghci说len_acc1 :: (?acc::Int) => [a] -> Int

  • 为什么第一个函数是单态的,而第二个不是?如果我的理解是正确的,反之亦然。

  • 或者它可能意味着类型是len_acc1 :: [a] -> Int,但每种情况都指?acc隐式值,所以我认为类型应该提到(?acc :: Int)约束。

  • 这种单态性如何意味着隐式参数没有传递给函数?

在Haskell中,我们经常将具有类型变量的类型签名称为多态的,比如多态函数id,其类型签名在这里包括类型变量a:

id :: a -> a

然而,这个签名是多态的,不是因为它包括类型变量a,而是因为该变量被量化。在Haskell中,类型签名被隐式地普遍量化,因此上面的等价于:

id :: forall a. a -> a

如果打开ExplicitForAll扩展,这实际上是可接受的语法。正是这种量化使类型具有多态性。

当Haskell在没有类型签名的情况下键入绑定时,它使用Hindley-Milner类型算法来分配类型。该算法的工作原理是将单形态类型签名分配给相互依赖的绑定集,然后通过确定它们之间的关系来细化它们。这些签名允许包含类型变量(!),但它们仍然被认为是单态的,因为变量没有被量化。也就是说,变量被想象成代表特定的类型,我们还没有弄清楚它们,因为我们正在进行类型检查。

一旦类型检查完成;最后的";已经分配了单形类型,还有一个单独的泛化步骤。所有保留在单态类型中的类型变量,如IntBool,都通过通用量化进行了推广。该步骤确定绑定的最终多态类型。约束是量化过程的一部分,因此当我们从单态类型转移到多态类型时,仅在泛化步骤中将其应用于类型签名。

该文档指的是这样一个事实,即在推断len_acc1的类型时,该算法为其分配一个单态类型,最终是带有自由(即未量化)类型变量a的最终单态类型len_acc1 :: [a] -> Int。虽然类型检查器会注意到需要(?acc :: Int)约束,但它并没有使这部分成为推断的len_acc1类型。特别地,分配给len_acc1的递归调用的类型是没有约束的单态类型[a] -> Int。只有在确定了len_acc1的最终单态类型之后,才能通过对a进行量化并添加约束来进行推广,以产生最终的顶级签名。

相反,当提供顶级多态签名时:

len_acc2 :: forall a. (?acc :: Int) => [a] -> Int

绑定len_acc2无论在哪里使用都是多态的(连同它的相关约束),特别是在递归调用中。

结果是,在没有提供(新的)?acc参数值的约束字典的情况下,len_acc1被递归地单形式地调用,而len_acc2被多形式地调用并且具有用于新的?acc值的约束词典。我认为,这种情况是隐含参数所独有的。否则,您不会遇到这样的情况:递归调用可以以单形式和多形式键入,这样代码在这两种类型下的行为就会不同。

你更有可能遇到以下情况;明显的";类型签名是必需的,因为不能推断出单形类型:

data Binary a = Leaf a | Pair (Binary (a,a)) deriving (Show)
-- following type signature is required... 
-- toList :: Binary a -> [a]
toList (Leaf x) = [x]
toList (Pair b) = concatMap ((x,y) -> [x,y]) (toList b)
main = print $ toList (Pair (Pair (Leaf ((1,2),(3,4)))))

我只能部分回答这个问题。

为什么ghci说len_acc1 :: (?acc::Int) => [a] -> Int

它是Int,因为第一个子句返回?acc,并且可以从第二个子句中的?acc + (1::Int)推断为Int

"在…右手边";

表示=之后的部分,其具有如上所述的类型Int,其是单态的。CCD_ 33将是形。

也可能意味着类型为len_acc1::[a]->Int,但每种情况都指?acc隐式值,所以我认为类型应该提到(?acc::Int)约束。

这似乎是在对我说;至少暂时被视为具有这种类型。但是我不明白为什么它会编译。

我试着阅读了定义隐式参数的论文,但没有找到答案。

最新更新