5

示例代码:

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

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

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

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

4

1 回答 1

4

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

请注意,由于Sample不是新类型,因此不能保证它只能容纳一个X而不是两个,更多或可变数量(Maybe X??Either X X)。因此,正如您将看到的,您的选项必须在结构内部做出明确的选择,这可能是导致自动派生 this 的扩展存在X的可能原因。

导出一个函数而不是多个函数

为了满足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具有多个 ( :*:) 字段 ( M1 S),第一个字段的类型为 ( Rec0) X

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

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

现在 for 的实例Wrapper可以写成:

data Wrapper = Wrapper
  { x :: X
  , i :: Int
  }
  deriving (Generic, HasX, Sample)
于 2019-05-03T11:08:55.523 回答