如何编写泛型函数获取双Record公共字段数组?支持Haskell方案
没问题,我来帮你搞定这个需求!先从TypeScript的实现说起,再给你Haskell的两种方案~
TypeScript 实现方案
要实现这个泛型函数,我们需要用泛型约束限定输入类型,再通过TypeScript的类型工具精准定义返回值类型:
function getCommonFields<T extends Record<string, unknown>, U extends Record<string, unknown>>(obj1: T, obj2: U): Array<Extract<keyof T, keyof U>> { // 把第一个对象的键存入Set,提升查找效率 const keysFromFirst = new Set(Object.keys(obj1)); // 过滤第二个对象的键,只保留两个对象共有的 return Object.keys(obj2).filter(key => keysFromFirst.has(key)) as Array<Extract<keyof T, keyof U>>; }
关键细节解释:
- 泛型约束
T extends Record<string, unknown>和U extends Record<string, unknown>:确保输入的两个参数都是键为字符串的对象,避免传入非对象类型导致错误。 - 返回值类型
Array<Extract<keyof T, keyof U>>:Extract是TypeScript内置的类型工具,它会从keyof T(T的所有键类型)中提取出同时存在于keyof U(U的所有键类型)中的类型,让返回数组的元素类型完全符合类型安全要求。 - 类型断言
as Array<...>:因为Object.keys默认返回string[],但我们通过逻辑过滤后确定这些键是公共键,所以可以安全地断言成目标类型。
Haskell 实现方案
Haskell里的"Record"有两种常见场景:一种是类似字典的键值对(用Data.Map实现),另一种是Haskell原生的Record类型(自定义代数数据类型的字段),我分别给你实现方案:
场景1:处理字典类型(类似JS对象)
如果你的"Record"指的是键值对集合,用Data.Map来实现非常简单:
import qualified Data.Map as Map import Data.List (intersect) -- 函数接收两个Map,返回它们的公共键列表 getCommonFields :: Ord k => Map.Map k v1 -> Map.Map k v2 -> [k] getCommonFields map1 map2 = Map.keys map1 `intersect` Map.keys map2
解释:
Ord k约束:因为Map的键必须是可比较的类型,所以需要这个约束。Map.keys会提取Map的所有键,intersect是列表的交集函数,直接取两个键列表的交集就是公共字段。
场景2:处理Haskell原生Record类型
如果你的"Record"指的是Haskell自定义的带字段的代数数据类型,我们需要借助GHC的泛型机制来提取字段名:
首先需要启用几个GHC扩展:
{-# LANGUAGE DeriveGeneric, TypeOperators, FlexibleContexts #-}
然后实现通用的字段提取和公共字段函数:
import GHC.Generics import Data.List (intersect) import Data.Proxy (Proxy(..)) -- 定义类型类,用于提取Record的字段名 class GetFields a where getFields :: proxy a -> [String] -- 借助泛型实现类型类实例 instance (Generic a, GGetFields (Rep a)) => GetFields a where getFields _ = gGetFields (from (undefined :: a)) -- 辅助类型类,处理泛型表示 class GGetFields f where gGetFields :: f a -> [String] -- 处理空构造器 instance GGetFields U1 where gGetFields _ = [] -- 处理字段选择器:提取字段名 instance (Selector s, GGetFields f) => GGetFields (M1 S s f) where gGetFields m = selName m : gGetFields (unM1 m) -- 处理多个字段的组合(Record的多个字段是用:*:连接的) instance (GGetFields f, GGetFields g) => GGetFields (f :*: g) where gGetFields (f :*: g) = gGetFields f ++ gGetFields g -- 忽略数据类型的元信息(构造器、模块名等) instance GGetFields f => GGetFields (M1 D d f) where gGetFields = gGetFields . unM1 instance GGetFields f => GGetFields (M1 C c f) where gGetFields = gGetFields . unM1 -- 最终的公共字段函数:接收两个Record类型的Proxy,返回公共字段名列表 getCommonRecordFields :: (GetFields a, GetFields b) => Proxy a -> Proxy b -> [String] getCommonRecordFields proxyA proxyB = getFields proxyA `intersect` getFields proxyB
使用示例:
定义两个Record类型并派生Generic:
data User = User { userName :: String, userAge :: Int } deriving Generic data Person = Person { userName :: String, personEmail :: String } deriving Generic
调用函数获取公共字段:
-- 会返回 ["userName"] commonFields :: [String] commonFields = getCommonRecordFields (Proxy :: Proxy User) (Proxy :: Proxy Person)
内容的提问来源于stack exchange,提问作者Sam R.
相关产品推荐
相关产品推荐

