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

时间:2017-04-27 20:30:10

标签: haskell monads applicative monoids

我记得这是非常基本的,甚至可以通过像(\a b -> (fst a + fst b, snd a + snd b) ) (1,2) (3,4)之类的lambda来完成模式匹配。但是,我认为应该有Haskell标准库提供的方法来做这样的事情。 mappend类型的(a,b)定义为Monoids看起来非常相似。但是做以下事情并不奏效:

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

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

3 个答案:

答案 0 :(得分:6)

数字是幺半群,这不是问题。问题是有两种不同的,同样好的方式,它们是幺半群 - 加法只有一种,另一种是乘法。因此标准库决定不给出一个monoid实例,我认为这是一个很好的决定。

Haskell允许 newtype wrappers 中的打包类型选择实例而不影响基础类型。两个不同的monoid实例可用于基础:

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

有点尴尬。有几种方法可以让你更简洁(我不会推荐其中任何一种;请参阅底部以获得更好的解决方案)。首先,正如Jon在注释中所说的那样,如果元组只包含像你的例子那样的普通数字文字,那么就没有必要在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 in一个去(并在 O (1)时间和空间,这也可以是一个额外的奖励。)

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}

如果您真的只需要添加剂monoid,我建议您使用专用的添加:AdditiveGroup

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

答案 1 :(得分:3)

如果您允许我忽略Monoid在您的问题中的作用,可以通过以下几种方式来表达Biapplicative from bifunctors,这是对Applicative的一种非常直接的概括。到Bifunctor s:

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)

答案 2 :(得分:1)

这也可以用一些简单而愚蠢但奇特的方式完成(虽然这种方法需要将 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)