是否可以为GADT创建一个单oid实例?



给定以下数据类型

{-# 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。为什么不呢?那么,mempty必须为所有 a具有类型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包中找到此类型类,在模块Data.Semigroup中;在此之前,您需要使用具有相同模块名称的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实例。

最新更新