从记录中的字段派生实例

Derive instance from field in record

示例代码:

{-# 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

在这里,我有一些 class 是由 X 实现的,然后是另一个包含 X.

的记录 Wrapper

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

我知道我可以通过获取字段并自己为每个函数调用它来做到这一点,如图所示。

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

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

这里有一些不需要任何扩展的策略,但要用一些前期成本来换取这些策略的易用性 类。

请注意,由于 Sample 不是新类型,因此不能保证它只会容纳一个 X 而不是两个,更多或可变数量(Maybe XEither X X?)。因此,正如您将看到的,您的选项必须在结构内显式选择 X,这可能是扩展自动派生为 not[=54= 的可能原因] 存在。

导出一个函数而不是多个

为了满足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 C) 的任何记录 (M1 D),该构造函数具有多个 (:*:) 字段 ( M1 S), 第一个字段是类型 (Rec0) X.

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

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

现在 Wrapper 的实例可以写成:

data Wrapper = Wrapper
  { x :: X
  , i :: Int
  }
  deriving (Generic, HasX, Sample)