2016-09-08 2 views
3

Учитывая следующий тип данныхВозможно ли создать экземпляр моноида для 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, или, если это действительно возможно, и имеет смысл?

+3

Ваш GADT на самом деле не GADT. Это похоже на 'newtype Response a = Response a', но не так полезно, потому что' a' может быть только 'Sum Int' или' HashMap Text (Sum Int) '. Если вы используете 'newtype', вы можете использовать' GeneralizedNewtypeDeriving', чтобы получить экземпляр «Monoid» бесплатно –

+0

@BenjaminHodgson, если бы я определял «Response a» как новый тип, у меня был бы способ определить функцию над ней, которая знает с чем он работает? – ppb

+4

Я увлекаюсь старомодным способом 'f :: Response (Sum Int) -> Foo' –

ответ

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, потому что вы даны a 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 экземпляра.