Haskell中如何泛化both函数以适配元组不同类型元素的自同态应用
问题分析
默认情况下,Haskell类型推断会给both生成类型(a -> b) -> (a, a) -> (b, b),这个类型要求元组的两个元素类型必须相同,且传入的f是单态函数。但你的需求是让both支持不同类型的元组元素,同时兼容id(可作用于任意类型)和concat . replicate 3(仅作用于列表)这类不同泛化程度的多态函数,因此需要更灵活的类型定义。
优化方案
方案一:使用Rank-2类型实现高泛化性(推荐)
通过开启RankNTypes语言扩展,给both定义Rank-2类型,允许传入的f是适配任意类型的多态函数,同时支持不同类型的元组元素。
{-# LANGUAGE RankNTypes #-} both :: (forall x. x -> f x) -> (a, b) -> (f a, f b) both f (x, y) = (f x, f y) foo :: ([Int], [Char]) foo = ([1], "a") bar :: ([Int], [Char]) bar = both (concat . replicate 3) foo -- 编译通过,结果: ([1,1,1], "aaa") -- 支持 both id 的调用 test :: (Int, String) test = both id (1, "a") -- 结果: (1, "a")
类型说明
forall x. x -> f x:表示f是一个多态函数,能接受任意类型x并返回f x类型的结果。这里的f可以看作类型构造器:- 传入
id时,f x等价于x本身,因此both id (a, b)直接返回(a, b); - 传入
concat . replicate 3时,f x等价于[x],因此会对元组的两个列表元素分别执行重复拼接操作。
- 传入
方案二:显式传入双函数(无需扩展)
如果不想启用语言扩展,可将both定义为接受一对函数的形式,虽然需要重复传入同一个函数,但同样能满足需求:
both :: (a -> c, b -> d) -> (a, b) -> (c, d) both (f, g) (x, y) = (f x, g y) bar :: ([Int], [Char]) bar = both (concat . replicate 3, concat . replicate 3) foo
内容的提问来源于stack exchange,提问作者shooqie
相关产品推荐
相关产品推荐

