Haskell中type重命名typeclass疑问:单例可行元组为何报错?
为什么第一个示例可行?
type关键字不仅能给具体类型(比如Int,种类为*)起别名,还能给更高阶的约束构造器起别名。Num的种类是* -> Constraint——它接受一个具体类型(比如Int),返回一个约束(比如Num Int)。
你写的type NumRenamed = Num本质是给这个约束构造器起了别名,所以NumRenamed a完全等价于Num a,这在类型签名的约束上下文里是合法的。至于:t命令显示Num a => a而非NumRenamed a => a,只是GHC的默认行为——它会自动展开类型别名,用原始的类型类来显示约束,不是代码有问题。
为什么元组形式的重命名会报错?
Haskell里的元组构造器(,)的种类是* -> * -> *,它要求两个参数必须是具体类型(种类为*)。但Num的种类是* -> Constraint,根本不满足元组构造器的参数要求,所以GHC会报错提示“需要更多参数”——因为Num本身不是一个具体类型,它需要接收一个具体类型才能变成约束,没法直接塞进元组里。
对比具体类型的情况:Int是种类为*的具体类型,所以type NumRenamed = Int是合法的,(Int, Int)也是合法的元组类型,:t显示别名是因为GHC对具体类型的别名会保留显示(除非必要才展开)。
解决方法:如何组合多个约束?
如果想把多个约束打包使用,不能用元组,而是要直接定义组合约束:
1. 打包针对同一类型的多个约束
比如要同时要求一个类型满足Num和Show:
type NumShow a = (Num a, Show a) foo :: NumShow a => a -> String foo x = show (x + 1)
2. 要求元组的两个元素都满足Num
如果想定义一个“元素都是Num类型的元组”的类型签名,直接写约束即可:
sillyNewFun :: (Num a, Num b) => (a, b) sillyNewFun = (2, 3)
如果想给这个约束组合起别名:
type TwoNumTuple a b = (Num a, Num b) sillyNewFun :: TwoNumTuple a b => (a, b) sillyNewFun = (2, 3)
3. 给“Num元素的元组”整体起别名(进阶)
如果想直接用一个别名表示“两个元素都是Num的元组”,可以结合类型相等约束:
import GHC.Exts (Constraint) type NumTuple t = (t ~ (a, b), Num a, Num b) => t sillyNewFun :: NumTuple t => t sillyNewFun = (2, 3)
内容的提问来源于stack exchange,提问作者HaveMercy

