【问题标题】:Find an element in an HList在 HList 中查找元素
【发布时间】:2016-02-07 10:54:14
【问题描述】:

我正在尝试编写两个函数来从 HList 中提取值,但我似乎无法让 GHC 满意。

第一个函数将具有签名extract :: HList a -> [b],它从列表中提取b 类型的所有元素。我只是通过要求a 中的类型拥有Typeable 实例才成功编写它。

class OfType a b where
    oftype :: a -> [Maybe b]

instance OfType (HList '[]) b where
    oftype = const []

instance (Typeable t, Typeable b, OfType (HList ts) b) => OfType (HList (t ': ts)) b where
    oftype (x :- xs) = (cast x :: Maybe b) : oftype xs

extract :: OfType a b => a -> [b]
extract = catMaybes . oftype

这是次优的,因为实际上并不需要 Typeable 约束来编写任何提取实例。

我尝试在约束中使用类型等式和不等式,但这只会给我重叠的实例。

我尝试编写的第二个函数将具有签名extract' :: Contains h n => HList h -> n,它提取列表中n 类型的第一个元素,并且上下文表明该列表实际上包含该类型的一个元素。

是否可以在没有Typeable 约束的情况下编写extract

是否可以在没有Typeable 约束的情况下编写extract'? 怎么写Contains

【问题讨论】:

  • 你想要的是Data.HList.Occurs中的hOccurshOccursMany。不过他们不支持Num a => HList '[Char, a, Char] 之类的东西。
  • 我似乎无法让hOccurs 工作,我正在使用hOccursFst (HCons 'a' HNil) :: Char。我错过了什么吗?
  • 对不起,我没有仔细检查。 hOccursFst 但不是 hOccurs 满足您的要求。再次抱歉。
  • 我的意思是 hOccursFst 不适合我,抱歉
  • 您能否将您的确切用例附加到您的问题中? hOccursFst (HCons 'a' HNil) :: Char 在 ghci 中为我工作。没有语言扩展,我唯一导入的是Data.HList

标签: haskell hlist


【解决方案1】:

由于您想在编译时检查类型相等性,我相信重叠实例是不可避免的(而且我不喜欢这些......)。

另外,我不能 100% 确定我得到了正确的重叠编译指示。

{-# LANGUAGE DataKinds, TypeOperators, ScopedTypeVariables,
    MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}
{-# OPTIONS -Wall #-}
module HListFilter where

import Data.HList.HList

class OfType a b where
    oftype :: a -> [b]

instance OfType (HList '[]) b where
    oftype = const []

instance {-# OVERLAPS #-} (OfType (HList ts) t) => OfType (HList (t ': ts)) t where
    oftype (HCons x xs) = x : oftype xs

instance {-# OVERLAPPABLE #-} (OfType (HList ts) b) => OfType (HList (t ': ts)) b where
    oftype (HCons _ xs) = oftype xs

test :: HList '[Int, Char, [Char], Char, Bool]
test = HCons (1::Int) (HCons 'a' (HCons "foo" (HCons 'b' (HCons True HNil))))

test_result :: [Char]
test_result = oftype test  -- "ab"

【讨论】:

  • 重叠的实例是可以避免的。我们可以从[*] 计算出一个[Bool] 提升列表,其中包含一个指示目标类型位置的封闭类型族,然后在此基础上使用非重叠类实现extract
  • @AndrásKovács 你能详细说明一下吗?
  • @AndrásKovács 那确实会更干净。我以某种方式想到了通过重叠方程定义的类型族,以生活在重叠实例的同一领域——我不知道它们是否会引发类似的问题。
  • AFAIK 封闭类型族消除了重叠实例的所有弊端。 GHC 不会通过一个方程来减少一个类型族,除非它可以证明没有一个早期的方程可以匹配。重叠实例的可疑优势在于,您可以在与其重叠的实例不同的模块中定义重叠实例,这也是一致性问题出现的地方。这对于封闭类型族来说是不可能的,因为它们是在单个声明中定义的。
  • @chi,我认为他们不会提出相同的连贯性 问题,但我认为他们可能会提出一些类似的灵活性问题。您可能必须处理平等的明确证据,或者更糟糕的是,正确的不平等,以处理动态问题。我不确定它会变得多么糟糕,但我自己也很怀疑它们。
【解决方案2】:

András Kovács 提到了类型族方法。这是一种方法:

type family Equal (x :: *) (y :: *) where
  Equal x x = 'True
  Equal x y = 'False

type family Check (b :: *) (as :: [*]) :: [Bool] where
  Check b '[] = '[]
  Check b (a ': as) = (b `Equal` a) ': Check b as

class ps ~ Check b as =>
    OfType (ps :: [Bool]) (as :: [*]) b where
  extract :: HList as -> [b]

ps ~ Check b as 超类上下文在这里很关键,只是时间问题。 GHC 总是在检查实例约束之前 提交一个实例,但它甚至在解决超类约束之后 之前都不会尝试找到一个实例。所以我们需要使用超类约束来固定选择哪些实例。

instance OfType '[] '[] b where
  extract HNil = []

instance (OfType ps as b, a ~ b) =>
           OfType ('True ': ps) (a ': as) b where
  extract (HCons x xs) = x : extract xs

instance (OfType ps as b, Equal b a ~ 'False) =>
    OfType ('False ': ps) (a ': as) b where
  extract (HCons _ xs) = extract xs

完成此操作后,您实际上可以编写一个避免“额外”类参数的接口:

class OfType' (as :: [*]) (b :: *) where
  extract' :: HList as -> [b]

instance OfType ps as b => OfType' as b where
  extract' = extract

Containsextract' 很容易。然而,编写好的Contains实例 需要与OfType 完全相同的箍跳。你想要的课程是这样的:

class Contains xs y where
  contains :: y `Elem` xs

-- Elem is part of the dependently typed folklore.
data Elem y xs where
  Here :: Elem y (y ': xs)
  There :: Elem y xs -> Elem y (x ': xs)

但是编写实例将再次迫使您进入重叠或封闭的类型系列。我知道我已经在 SO 附近的某个地方编写了这段代码,但是您应该能够很容易地计算出重叠版本;类型家族版本通常会遵循与OfType 相同的模式。

【讨论】:

  • 这里没什么好补充的。与其他人如何做到这一点存在细微的风格差异,例如herehere,也就是说,他们使用带有Proxy-ed 方法的类和(可能)额外的Proxy-less top-水平功能。在当前情况下看起来像 this
  • @AndrásKovács,这种推迟大等式约束直到转义类的技术会使类型检查更快吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-08
  • 2023-04-10
  • 2017-12-03
  • 2012-12-12
  • 1970-01-01
相关资源
最近更新 更多