将 (a1,b1) 和 (a2,b2) 映射到 (a1+a2, b1+b2)

Mappend (a1,b1) and (a2,b2) into (a1+a2, b1+b2)

我记得这是非常基本的,甚至可以通过像 (\a b -> (fst a + fst b, snd a + snd b) ) (1,2) (3,4) 这样的 lambda 来简单地通过模式匹配来完成。但是,我认为 Haskell 标准库应该提供一些方法来做这样的事情。 mappend for (a,b) type as Monoids 的定义看起来非常相似。但执行以下操作无效:

(1,2) `mappend` (3,4)

任何 Haskell 添加两个 2 元组的方法?

数字是幺半群,这不是问题所在。问题是有两种不同的,同样好的方式它们是幺半群——加法只有一种,另一种是乘法。标准库因此决定完全不提供幺半群实例,我认为这是一个很好的决定。

Haskell 允许将 newtype wrappers 中的类型打包到 select 实例而不影响基础类型。两种不同的幺半群实例在 base:

中可用
Prelude Data.Monoid> case (Sum 1, Sum 2)<>(Sum 3, Sum 4) of (Sum a, Sum b) -> (a,b)
(4,6)

有点尴尬。有几种方法可以使它更简洁(我真的不推荐其中任何一种;请参阅底部以获得更好的解决方案)。首先,正如乔恩在评论中所说,如果元组像您的示例中那样仅包含纯数字文字,则无需将它们显式包装在 Sum 构造函数中,因为有一个实例 Num a => Num (Sum a) 一个数字文字是多态的,即以下内容:

Prelude Data.Monoid> case (1,2) <> (3,4) of (Sum x, Sum y) -> (x,y)
(4,6)

但是,如果元组元素的类型已经固定...

Prelude Data.Monoid> let [a,b,c,d] = [1,2,3,4] :: [Int]
Prelude Data.Monoid> case (a,b) <> (c,d) of (Sum x, Sum y) -> (x,y) :: (Int,Int) 
<interactive>:6:25:
    Couldn't match expected type ‘Int’ with actual type ‘Sum Int’
    In the pattern: Sum x
    In the pattern: (Sum x, Sum y)
    In a case alternative: (Sum x, Sum y) -> (x, y) :: (Int, Int)

在这里您需要明确包装应该发生的位置。您仍然可以使用 type-safe coercions 稍微简化一下,它允许您将整个容器的所有元素(元组、列表、映射、数组...)包装在一个新类型中,例如 Sum一次(在 O (1) 时间&space,这也是一个很好的奖励)。

Prelude Data.Monoid Data.Coerce> case coerce (a,b) <> coerce (c,d) of (Sum x, Sum y) -> (x,y) :: (Int,Int)
(4,6)

使用 Newtype class:

的方法可能更简洁,对本地签名的依赖也更少
Prelude Data.Monoid> :m +Control.Newtype
Prelude Data.Monoid Control.Newtype> :m +Control.Arrow
Prelude Data.Monoid Control.Newtype Control.Arrow> :simpleprompt 
> ala (Sum***Sum) foldMap [(1,2), (3,4)]
(4,6)

...然而,正如我现在相当惊讶地发现的那样,newtype 库没有附带为此所必需的元组实例。你可以自己定义它:

> :set -XFunctionalDependencies -XUndecidableInstances
> instance (Newtype a α, Newtype b β) => Newtype (a,b) (α,β) where {pack=pack***pack; unpack=unpack***unpack}

如果你真的只需要加法幺半群,我建议你使用专用的 class 作为加法:AdditiveGroup

Prelude Data.AdditiveGroup> (1,2)^+^(3,4)
(4,6)

这也可以通过一些简单、愚蠢但奇特的方式来完成(尽管这种方法需要将您的 Haskell 转换为 Lisp).

{-# LANGUAGE PostfixOperators #-}

import Data.Bifunctor (bimap)
import Data.Semigroup (Sum (..), (<>))

(+?) :: (a, b) -> (Sum a, Sum b)
(+?) = bimap Sum Sum

(+!) :: (Sum a, Sum b) -> (a, b)
(+!) = bimap getSum getSum

然后在ghci:

ghci> (( ((1,2) +?) <> ((3,4) +?)) +!)
(4,6)

如果您允许我忽略 Monoid 在您的问题中的作用,这里有几种用 Biapplicative from bifunctors 表达它的方式,这是 [=13 的非常简单的概括=] 到 Bifunctors:

GHCi> import Data.Biapplicative
GHCi> :t bimap
bimap :: Bifunctor p => (a -> b) -> (c -> d) -> p a c -> p b d
GHCi> :t (<<*>>)
(<<*>>) :: Biapplicative p => p (a -> b) (c -> d) -> p a c -> p b d
GHCi> bimap (+) (+) (1,2) <<*>> (3,4)
(4,6)
GHCi> :t biliftA2
biliftA2
  :: Biapplicative w =>
     (a -> b -> c) -> (d -> e -> f) -> w a d -> w b e -> w c f
GHCi> biliftA2 (+) (+) (1,2) (3,4)
(4,6)