从记录中的字段派生实例

时间:2019-05-03 07:17:29

标签: haskell deriving

示例代码:

{-# LANGUAGE NamedFieldPuns #-}

module Sample where

class Sample a where
  isA :: a -> Bool
  isB :: a -> Bool
  isC :: a -> Bool

data X =
  X

instance Sample X where
  isA = undefined
  isB = undefined
  isC = undefined

data Wrapper = Wrapper
  { x :: X
  , i :: Int
  }

instance Sample Wrapper where
  isA Wrapper {x} = isA x
  isB Wrapper {x} = isB x
  isC Wrapper {x} = isC x

在这里,我有一个由X实现的类,然后是另一个包含Wrapper的记录X

我希望Wrapper通过其Sample字段派生x实例。

我知道我可以通过获取该字段并针对每个函数自己调用它来实现,如图所示。

是否有一些标志或某种方法可以自动执行或仅执行一次?

这似乎类似于DerivingViaGeneralisedNewtypeDeriving,但两者似乎都只针对newtype或强制类型

1 个答案:

答案 0 :(得分:4)

这里有些策略不需要任何扩展,但是为了便于派生这些类而付出了一些前期成本。

请注意,由于Sample不是新类型,因此无法保证它只能容纳一个X,而不能容纳两个,更多或可变数量(Maybe XEither X X ?)。因此,正如您将看到的那样,您的选项必须使结构内部的X选择明确,这可能是扩展存在的原因,该扩展会自动将其导出为 not

派生一个功能而不是多个功能

要满足Sample,我们确实需要X。让我们将其作为类型类:

class HasX t where
  getX :: t -> X

class Sample t where
  isA :: t -> Bool
  isB :: t -> Bool
  isC :: t -> Bool
  default isA :: HasX t => t -> Bool
  isA = isA . getX
  default isB :: HasX t => t -> Bool
  isB = isB . getX
  default isC :: HasX t => t -> Bool
  isC = isC . getX

instance HasX Wrapper where
  getX = x

instance Sample Wrapper -- no implementation necessary

通过泛型派生

比方说,我们只想处理以X作为第一个字段的记录。为了匹配类型结构,我们可以使用GHC.Generics。在这里,我们为HasX添加了一种默认设置为第一个字段的方法:

class HasX t where
  getX :: t -> X
  default getX :: (Generic a, HasX (Rep a)) => t -> X
  getX = getX . from

instance HasX (M1 D d (M1 C c (M1 S s (Rec0 X) :*: ff))) o where
  getX (M1 (M1 ((M1 (K1 x)) :*: _))) = x

HasX的最后一个实例使用单个构造函数(M1 D)匹配任何记录(M1 C),该构造函数具有多个(:*:)字段({{ 1}}),第一个字段的类型为(M1 SRec0

(是的,通用实例笨拙。欢迎编辑。)

(要查看X通用类型的确切表示,请在GHCi控制台中检查Wrapper。)

现在Rep Wrapper的实例可以写为:

Wrapper