Mappend (a1,b1) и (a2,b2) в (a1+a2, b1+b2)

Я помню, что это очень просто, и может быть просто сделано с сопоставлением с образцом даже через лямбда-подобие (\a b -> (fst a + fst b, snd a + snd b) ) (1,2) (3,4), Тем не менее, я думаю, что в стандартной библиотеке Haskell должны быть способы, позволяющие делать такие вещи. Определение mappend за (a,b) Тип как моноиды выглядит довольно похоже. Но сделать следующее не сработало:

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

Есть ли способ добавить два 2-х кортежа на Haskell?

3 ответа

Решение

Числа моноиды, это не проблема. Проблема в том, что есть два разных, одинаково хороших способа, которыми они являются моноидами - сложение только одно, другое умножение. Таким образом, стандартная библиотека решила вообще не давать моноидный экземпляр, что я считаю хорошим решением.

Haskell позволяет типам упаковки в оболочках нового типа выбирать экземпляр, не затрагивая базовый тип. Два разных экземпляра моноида доступны в базе:

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)

Здесь необходимо четко указать, где должно произойти обертывание. Вы все еще можете немного его уточнить, используя безопасные приведения типа, которые позволяют вам обернуть все элементы целого контейнера (кортежи, списки, карты, массивы...) в новый тип, такой как Sum за один раз (и в 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 класс:

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}

Если вам действительно нужен только аддитивный моноид, я рекомендую вместо этого использовать специальный класс для добавления: AdditiveGroup

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

Если вы позволите мне упустить из виду роль Monoid в вашем вопросе, вот несколько способов выразить это с точки зрения Biapplicative от бифункторов, что является довольно простым обобщением Applicative в 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)

Это также может быть сделано простым и глупым, но причудливым способом (хотя этот подход требует превращения вашего 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)
Другие вопросы по тегам