Автоматическое распространение моноидального маппенда

Скажем, у меня есть моноид, определенный следующим образом:

data TotalLine = TotalLine { totalQuantity :: Int, orderTotal :: Float }

instance Monoid TotalLine where
  mempty = zero
  mappend = add

Поскольку totalQuantity и orderTotal являются числами, они также образуют моноид под (+). Есть ли способ определить

add :: TotalLine -> TotalLine -> TotalLine

Итак, могу ли я просто распространить вызов mappend для каждого поля вместо того, чтобы вручную определять что-то вроде

add line1 line2 =
  TotalLine {
    totalQuantity = totalQuantity line1 + totalQuantity line2,
    orderTotal = orderTotal line1 + orderTotal line2
   }

person Batou99    schedule 18.03.2016    source источник


Ответы (3)


Там generic-deriving пакет точно соответствует вашему требованию:

{-# LANGUAGE DeriveGeneric #-}

import           Generics.Deriving.Monoid
import           GHC.Generics

data T = T {str :: String, str' :: String}
  deriving (Generic, Show)

main = undefined

instance Monoid T where
  mempty = memptydefault
  mappend = mappenddefault

И некоторые результаты от ghci:

> T "a" "b" `mappend` T "c" "d"
< T {str = "ac", str' = "bd"}

Но это не работает из коробки для вашего типа данных TotalLine, так как и Int, и Float не имеют экземпляра Monoid.

Также добавлять зависимость только для этой цели не так экономично. Вам лучше реализовать экземпляр Monoid вручную.


Было некоторое обсуждение того, почему GHC не может создавать экземпляры Monoid, это оказалось, что в общем случае такой производный экземпляр не может быть уникальным.Но в частном случае, когда тип данных имеет только один конструктор с конкретными и моноидальными полями, производный экземпляр уникален.

person zakyggaps    schedule 18.03.2016
comment
но в особом случае, когда тип данных имеет только один конструктор с конкретными и моноидальными полями, производный экземпляр уникален. Хотя существует натуральный моноид, он все же не уникален. Это может mappend некоторые поля в обратном порядке. - person PyRulez; 18.03.2016

Обратите внимание, что существует несколько моноидов с доменом Int (Nat, Float, ..), поэтому мне было бы очень сложно читать код, содержащий instance Monoid Int. Тогда mappend может быть любым плюсом, временем, максимумом, минимумом и многим другим.

person d8d0d65b3f7cf42    schedule 18.03.2016
comment
Небольшое примечание: сначала меня смутила скобка, так как я неправильно понял ее, подразумевая, что Nat, Float,... являются отдельными моноидами с доменом Int, что не имеет смысла. - person chi; 18.03.2016

Если вы можете немного изменить тип (но он все еще изоморфен вашему), вы можете попробовать следующее:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Data.Monoid

newtype TotalLine = TotalLine (Sum Int, Sum Float) deriving Monoid
person PyRulez    schedule 18.03.2016