给定以下数据类型
{-# 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
实例。