Haskell:如何在where子句中引用函数签名的类型变量?
问题:Haskell where子句中如何引用外层函数的类型变量?
先看这段可正常运行的代码:
data Test a = Test { sample :: [a], underflow :: a, width :: a, bounds :: Int -> a } test :: Num a => [a] -> a -> a -> Test a test sample underflow width = Test { sample, underflow, width, bounds } where -- bounds :: Int -> a bounds 0 = underflow bounds i = width + bounds (i - 1) main :: IO () main = print $ map (bounds t) [1..10] where t = test [1..6] 3 5
一旦取消注释where子句里的bounds :: Int -> a,代码就编译失败——因为这里的a是一个全新的类型变量,和test函数签名里的a完全不相关,编译器会认为你要给bounds指定一个和外层无关的类型,自然报错。
解决方法:使用ScopedTypeVariables扩展
这是最直接的解决方案,让内层where子句的类型变量能引用外层函数的类型变量:
- 在文件顶部开启扩展:
{-# LANGUAGE ScopedTypeVariables #-}
- 修改
test函数的类型签名,用forall显式量化类型变量a,明确它的作用域:
test :: forall a. Num a => [a] -> a -> a -> Test a
- 现在就可以取消注释
bounds的类型签名了,此时这里的a和test里的a是同一个类型变量。
修改后的完整代码:
{-# LANGUAGE ScopedTypeVariables #-} data Test a = Test { sample :: [a], underflow :: a, width :: a, bounds :: Int -> a } test :: forall a. Num a => [a] -> a -> a -> Test a test sample underflow width = Test { sample, underflow, width, bounds } where bounds :: Int -> a bounds 0 = underflow bounds i = width + bounds (i - 1) main :: IO () main = print $ map (bounds t) [1..10] where t = test [1..6] 3 5
原理说明
Haskell默认规则下,每个类型签名里的类型变量都是“局部”的,where子句里的类型签名会被当成独立的,所以需要ScopedTypeVariables扩展打破这个限制,通过forall把类型变量的作用域扩展到整个函数(包括where子句),这样内层就能复用外层的类型变量了。
内容的提问来源于stack exchange,提问作者Chirmol Studio
相关产品推荐
相关产品推荐

