You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写泛型函数获取双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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:22:09