Haskell中HList如何实现按索引取值与元素存在校验功能
HList 对应功能实现方案
HList是元素类型可异构的列表结构,无法直接套用普通同质列表的实现逻辑,需要通过类型类完成类型层面的推导,以下是具体实现:
1. 类似(!!)的按索引取元素函数
由于HList不同位置的元素类型可能不同,按索引取值的返回类型由索引位置决定,优先推荐使用类型级自然数做索引,这也是HList场景下的常规用法;如果需要接收运行时传入的整数n,可以通过存在类型包装返回结果。
首先开启需要的语言扩展,导入依赖:
{-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE TypeFamilies #-} import GHC.TypeLits
HList的基础结构定义(使用HList包时可直接复用内置实现,无需重复定义):
data HList (ts :: [*]) where HNil :: HList '[] HCons :: t -> HList ts -> HList (t ': ts)
实现类型级索引的类型类:
class HIndex (n :: Nat) ts where type IndexRes n ts :: * hIndex :: HList ts -> IndexRes n ts -- 基例:索引为0时返回列表首元素 instance HIndex 0 (t ': ts) where type IndexRes 0 (t ': ts) = t hIndex (HCons x _) = x -- 递归例:索引大于0时,递归在尾部列表取n-1位置的元素 instance HIndex (n-1) ts => HIndex n (_ ': ts) where type IndexRes n (_ ': ts) = IndexRes (n-1) ts hIndex (HCons _ xs) = hIndex @(n-1) xs
如果需要支持传入运行时Int作为参数,通过存在类型包装返回值即可:
-- 存在类型,隐藏返回元素的具体类型 data HElem where HElem :: t -> HElem hIndexRuntime :: Int -> HList ts -> Maybe HElem hIndexRuntime n _ | n < 0 = Nothing hIndexRuntime n hl = go n hl where go 0 (HCons x _) = Just (HElem x) go k (HCons _ xs) = go (k-1) xs go _ HNil = Nothing
生产环境直接使用HList包内置的索引函数即可,无需重复实现上述逻辑。
2. 类似elem的元素存在判断函数
该函数最终返回Bool类型,不需要处理异构返回类型,只需遍历HList,对类型与待查元素一致的项通过Eq实例判断相等性即可:
class HElem e ts where hElem :: e -> HList ts -> Bool -- 基例:空列表不存在任何元素 instance HElem e '[] where hElem _ HNil = False -- 递归例:当前首元素类型与待查元素类型不一致时,直接递归判断尾部 instance {-# OVERLAPPABLE #-} HElem e ts => HElem e (t ': ts) where hElem e (HCons _ xs) = hElem e xs -- 递归例:当前首元素类型与待查元素类型一致时,先判断相等,不相等再递归判断尾部 instance {-# OVERLAPS #-} Eq e => HElem e (e ': ts) where hElem e (HCons x xs) = e == x || hElem e xs
使用示例
-- 构造测试用异构列表,包含Int、String、Bool三种类型元素 testHList :: HList '[Int, String, Bool] testHList = HCons 123 $ HCons "test" $ HCons True HNil -- 取索引为1的元素(从0开始计数),返回值类型为String strVal :: String strVal = hIndex @1 testHList -- 结果为"test" -- 元素存在判断 check1 :: Bool check1 = hElem (123 :: Int) testHList -- 结果为True check2 :: Bool check2 = hElem False testHList -- 结果为False check3 :: Bool check3 = hElem 'a' testHList -- 结果为False,列表中无Char类型元素
内容的提问来源于stack exchange,提问作者Rui Azevedo
相关产品推荐
相关产品推荐

