Можно ли создать экземпляр моноида для 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, или если это действительно возможно или имеет смысл?


person ppb    schedule 08.09.2016    source источник
comment
Ваш GADT на самом деле не GADT. Это похоже на newtype Response a = Response a, но не так полезно, потому что a может быть только Sum Int или HashMap Text (Sum Int). Если вы используете newtype, вы можете использовать GeneralizedNewtypeDeriving, чтобы получить экземпляр Monoid бесплатно.   -  person Benjamin Hodgson♦    schedule 08.09.2016
comment
@BenjaminHodgson, если бы я определил Response a как новый тип, был бы у меня способ определить над ним функцию, которая знает, с чем a она работает?   -  person ppb    schedule 09.09.2016
comment
Я люблю старомодный способ f :: Response (Sum Int) -> Foo   -  person Benjamin Hodgson♦    schedule 09.09.2016


Ответы (1)


Как вы заметили, вы не можете предоставить 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.

person Antal Spector-Zabusky    schedule 08.09.2016