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

时间:2016-09-08 19:18:49

标签: haskell gadt

给出以下数据类型

{-# LANGUAGE GADTs #-}

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

我如何为它推导出一个monoid实例?对于mappend的定义,我可以在构造函数上进行模式匹配

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

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

1 个答案:

答案 0 :(得分:8)

正如您所注意到的,您无法提供instance Monoid (Response a),因为您无法定义mempty :: Response a。为什么不?好吧,mempty必须为所有 Response a添加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个实例。