是否可以为 GADT 创建一个幺半群实例?

Is it possible to create a monoid instance for a GADT?

给定以下数据类型

{-# LANGUAGE GADTs #-}

data Response a where
  ResponseMap :: HashMap Text (Sum Int) -> Response (HashMap Text (Sum Int))
  ResponseSum :: Sum Int -> Response (Sum Int)

我如何为它派生一个幺半群实例?对于 mappend 的定义,我可以在构造函数上进行模式匹配

  (ResponseSum v1) `mappend` (ResponseSum v2) = undefined
  (ResponseMap v1) `mappend` (ResponseMap v2) = undefined

并轻松组合这些值,但我不知道我将如何实施 mempty,或者它是否确实可行或有意义?

如您所见,您无法提供 instance Monoid (Response a),因为您无法定义 mempty :: Response a。为什么不?那么,对于 all amempty 必须具有类型 Response a,包括 Bool。但是你不能构造Response Bool类型的值,只能构造Response (HashMap Text (Sum Int))Response (Sum Int)。所以你将无法创建 mempty。这对 mappend 来说不是问题,因为你 给了 一个 Response a,所以你可以检查给了你哪个 a。但是mempty没有什么可分析的

那你能做什么?嗯,首先,你可以提供一个instance Semigroup (Response a)。一个半群就是一个没有 mempty 的幺半群,所以这正是你想要的。从 GHC 8 开始,您可以在 base 包中找到此类型 class,在模块 Data.Semigroup; prior to that, you need to use the semigroups 包中,具有相同的模块名称。它使用二元运算符 (<>) 而不是 mappend。所以你有

import Data.Semigroup
import Data.Monoid hiding ((<>))

-- ...

instance Semigroup (Response a) where
  ResponseMap v1 <> ResponseMap v2 = ResponseMap $ v1 <> v2
  ResponseSum v1 <> ResponseSum v2 = ResponseSum $ v1 <> v2

您还可以为您可以构造的类型索引提供特定 Monoid 实例。使用 FlexibleInstances,看起来像

{-# LANGUAGE FlexibleInstances #-}

instance Monoid (Response (HashMap Text (Sum Int))) where
  mempty = ResponseMap mempty
  mappend = (<>)

instance Monoid (Response (Sum Int)) where
  mempty = ResponseSum mempty
  mappend = (<>)

现在,对于您 知道单位是什么的情况,您有一个 Monoid 实例。