monoidal mappend 的自动传播

Automatic propagation of monoidal mappend

假设我定义了一个幺半群:

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

instance Monoid TotalLine where
  mempty = zero
  mappend = add

因为 totalQuantityorderTotal 都是数字,所以它们在 (+)[=23= 下也形成一个幺半群] 有没有办法定义

add :: TotalLine -> TotalLine -> TotalLine

所以,我可以只在每个字段上传播 mappend 调用,而不必手动定义类似

的内容吗
add line1 line2 =
  TotalLine {
    totalQuantity = totalQuantity line1 + totalQuantity line2,
    orderTotal = orderTotal line1 + orderTotal line2
   }

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 数据类型,它不是开箱即用的,因为 IntFloat 都没有 Monoid 的实例.

还添加一个依赖项只是为了这个目的不太经济。您最好手动实现 Monoid 实例。


关于为什么 GHC 不能派生 Monoid 实例已经some discussion,结果证明通常这样的派生实例可能不是唯一的。但是在数据类型只有的特殊情况下一个具有具体和幺半群字段的构造函数派生实例是唯一的。

请注意,有几个具有域 Int 的幺半群(Nat、Float、..) 所以我会发现阅读具有 instance Monoid Int 的代码非常混乱。然后 mappend 可以是加号、乘号、最大值、最小值等等中的任何一个。

如果你可以稍微改变一下类型(但它仍然与你的同构),你可以试试这个:

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

newtype TotalLine = TotalLine (Sum Int, Sum Float) deriving Monoid