初步说明:这是a deleted question by SeanD的重新定位。
就像列表中有zipWith
一样......
GHCi> zipWith (+) [1,2] [3,4]
[4,6]
......在......的精神中,感觉应该有类似于元组的东西。
tupleZipWith (+) (1,2) (3,4)
...但 base 中似乎没有任何明显的相似之处。我有哪些选择?
答案 0 :(得分:10)
一个选项是使用tuples-homogenous-h98包,它为具有适当Applicative
个实例的同类元组提供newtype包装:
GHCi> import Data.Tuple.Homogenous
GHCi> import Control.Applicative
GHCi> liftA2 (+) (Tuple2 (1,2)) (Tuple2 (3,4))
Tuple2 {untuple2 = (4,6)}
GHCi> (>) <$> Tuple3 (7,4,7) <*> Tuple3 (6,6,6)
Tuple3 {untuple3 = (True,False,True)}
如果你有一个最喜欢的同源元组/固定大小的矢量/固定大小的列表库而不是 tuples-homogenous-h98 ,那么它很可能也会有合适的ZipList
- 比如Applicative
个实例。
对于成对的问题略有不同,您可能需要考虑来自 bifunctors的Data.Biapplicative
:
GHCi> import Data.Biapplicative
GHCi> bimap (+) (+) (1,2) <<*>> (3,4)
(4,6)
这种方法的一个好处是它可以处理异构对:
GHCi> bimap (+) (+) (1,2.5) <<*>> (3,4)
(4,6.5)
GHCi> bimap (+) (++) (1,"foo") <<*>> (3,"bar")
(4,"foobar")
答案 1 :(得分:5)
使用GHC Generics,我们可以定义仅依赖于类型结构的操作(构造函数及其arities)。
我们想要一个函数zipWithP
,它接受一个函数f
,并在匹配的字段之间压缩两个元组,应用f
。或许带有符合此标志的东西:
zipWithP
:: forall c s. _
=> (forall s. c s => s -> s -> s) -> a -> a -> a
这里f :: forall s. c s => s -> s -> s
是多态的,允许元组是异构的,只要这些字段都是c
的实例。该要求将由_
约束捕获,该约束由实现决定,只要它有效。
有些库可以捕获常见结构,特别是one-liner和generics-sop。
按自动化程度递增......
经典的解决方案是使用GHC.Generics
模块。 Generic
实例表示用户定义的类型a
和&#34;通用表示&#34;之间的同构。 Rep a
与之相关联。
此通用表示由GHC.Generics
中定义的一组固定类型构成。 (该模块的文档包含有关该表示的更多详细信息。)
标准步骤是:
在固定的一组类型上定义函数(可能是它的一个子集);
通过使用Generic
实例给出的同构来使它们适应用户定义的类型。
步骤1通常是类型类。这里GZipWith
是可以压缩的通用表示类。这里处理的类型构造函数按重要性递减顺序:
K1
代表字段(只需应用f
); (:*:)
代表类型产品(分别压缩操作数); M1
newtype包含类型级别的信息,我们不在这里使用,所以我们只需用它包装/解包; U1
代表了无效的构造函数,主要是为了完整性。第2步通过在适当情况下将zipWithP
与gZipWith
/ from
合并来定义to
。
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
import GHC.Generics
class GZipWith c f where
gZipWith :: (forall s. c s => s -> s -> s) -> f p -> f p -> f p
instance c a => GZipWith c (K1 _i a) where
gZipWith f (K1 a) (K1 b) = K1 (f a b)
instance (GZipWith c f, GZipWith c g) => GZipWith c (f :*: g) where
gZipWith f (a1 :*: a2) (b1 :*: b2) = gZipWith @c f a1 b1 :*: gZipWith @c f a2 b2
instance GZipWith c f => GZipWith c (M1 _i _c f) where
gZipWith f (M1 a) (M1 b) = M1 (gZipWith @c f a b)
instance GZipWith c U1 where
gZipWith _ _ _ = U1
zipWithP
:: forall c a. (Generic a, GZipWith c (Rep a))
=> (forall s. c s => s -> s -> s) -> a -> a -> a
zipWithP f a b = to (gZipWith @c f (from a) (from b))
main = do
print (zipWithP @Num (+) (1,2) (3,4) :: (Int, Integer))
generics-sop提供高级组合器,通常使用感觉像fmap
/ traverse
/ zip
...
在这种情况下,相关的组合子是hcliftA2
,它使用二进制函数来压缩通用的异构元组元组。代码后的更多解释。
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Applicative (liftA2)
import Data.Proxy (Proxy(..))
import Generics.SOP
zipWithP
:: forall c a k
. (Generic a, Code a ~ '[k], All c k)
=> (forall s. c s => s -> s -> s) -> a -> a -> a
zipWithP f x y =
case (from x, from y) of
(SOP (Z u), SOP (Z v)) ->
to (SOP (Z (hcliftA2 (Proxy :: Proxy c) (liftA2 f) u v)))
main = do
print (zipWithP @Num (+) (1,2) (3,4) :: (Int, Integer))
从zipWithP
的顶部开始。
约束:
Code a ~ '[k]
:a
必须是单一构造函数类型(Code a :: [[*]]
是a
的构造函数列表,每个都作为其字段列表给出。 / LI>
All c k
:构造函数k
的所有字段都满足约束c
。体:
from
从普通类型a
映射到通用产品总和(SOP I (Code a)
)。a
有一个构造函数。我们通过模式匹配来应用这些知识来摆脱&#34; sum&#34;层。我们得到u
和v
,其类型为产品(NP I k
)。hcliftA2
来压缩两个元组u
和v
。I
/ Identity
(functor-functor或HKD样式)中,因此顶部还有一个liftA2
图层f
。to
(from
的反转)从前两个步骤向后退。有关详细信息,请参阅generics-sop文档。
zipWithP
属于一类操作,通常由&#34;为每个字段&#34;执行此操作。 one-liner导出操作,其中一些名称可能看起来很熟悉(map...
,traverse...
),这些操作基本上是单个&#34;广义遍历的特化。与任何通用类型相关联。
特别是,zipWithP
被称为binaryOp
。
{-# LANGUAGE TypeApplications #-}
import Generics.OneLiner
main = print (binaryOp @Num (+) (1,2) (3,4) :: (Int, Integer))