是否有一个用于元组的zipWith模拟?

时间:2018-04-25 23:24:44

标签: haskell tuples applicative

初步说明:这是a deleted question by SeanD的重新定位。

就像列表中有zipWith一样......

GHCi> zipWith (+) [1,2] [3,4]
[4,6]

......在......的精神中,感觉应该有类似于元组的东西。

tupleZipWith (+) (1,2) (3,4)

...但 base 中似乎没有任何明显的相似之处。我有哪些选择?

2 个答案:

答案 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-linergenerics-sop

按自动化程度递增......

经典的解决方案是使用GHC.Generics模块。 Generic实例表示用户定义的类型a和&#34;通用表示&#34;之间的同构。 Rep a与之相关联。

此通用表示由GHC.Generics中定义的一组固定类型构成。 (该模块的文档包含有关该表示的更多详细信息。)

标准步骤是:

  1. 在固定的一组类型上定义函数(可能是它的一个子集);

  2. 通过使用Generic实例给出的同构来使它们适应用户定义的类型。

  3. 步骤1通常是类型类。这里GZipWith是可以压缩的通用表示类。这里处理的类型构造函数按重要性递减顺序:

    • K1代表字段(只需应用f);
    • (:*:)代表类型产品(分别压缩操作数);
    • M1 newtype包含类型级别的信息,我们不在这里使用,所以我们只需用它包装/解包;
    • U1代表了无效的构造函数,主要是为了完整性。

    第2步通过在适当情况下将zipWithPgZipWith / 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;层。我们得到uv,其类型为产品NP I k)。
    • 我们应用hcliftA2来压缩两个元组uv
    • 字段包含在类型构造函数I / Identityfunctor-functorHKD样式)中,因此顶部还有一个liftA2图层f
    • 我们获得了一个新的元组,并通过应用构造函数和tofrom的反转)从前两个步骤向后退。

    有关详细信息,请参阅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))