dup*_*ode 8 haskell tuples applicative
初步说明:这是SeanD 删除的问题的重新定位.
就像zipWith列表一样......
GHCi> zipWith (+) [1,2] [3,4]
[4,6]
......感觉就像元素一样,应该有类似的东西......
tupleZipWith (+) (1,2) (3,4)
......但是在基地似乎没有任何明显的东西.我有哪些选择?
dup*_*ode 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实例.
对于成对的问题略有不同,您可能需要考虑Data.Biapplicative来自bifunctors:
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")
使用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.只要有效,_约束就会捕获该要求,这取决于实现.
随着自动化程度的提高......
经典的解决方案是使用该GHC.Generics模块.甲Generic实例表示用户定义类型之间的同构a和一个"一般表示" Rep a与它相关联.
此通用表示由一组固定的类型构成GHC.Generics.(该模块的文档包含有关该表示的更多详细信息.)
标准步骤是:
在固定的一组类型上定义函数(可能是它的一个子集);
通过使用Generic实例给出的同构,使它们适应用户定义的类型.
步骤1通常是类型类.以下GZipWith是可以压缩的通用表示类.这里处理的类型构造函数按重要性降序排列:
K1代表字段(只是申请f);(:*:) 代表类型产品(分别压缩操作数);M1NEWTYPE携带在类型级别的信息,我们使用的不是这里,所以我们只是包装/用它打开;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,每个构造函数都作为其字段列表给出).All c k:构造函数的所有字段都k满足约束c.身体:
from从常规类型映射a到通用的产品总和(SOP I (Code a)).a有一个构造函数.我们通过模式匹配来应用这些知识来摆脱"总和"层.我们得到u和v,其类型产品(NP I k).hcliftA2拉链两个元组u和v.I/ Identity(函子函子或HKD样式)中,因此liftA2顶部还有一个图层f.to(反过来)从前两个步骤向后移动from.有关更多详细信息,请参阅generics-sop文档.
zipWithP属于一类通常由"为每个字段执行此操作"描述的操作.单行导出操作,其中一些名称可能看起来很熟悉(map...,traverse...),它们本质上是与任何泛型类型相关联的单个"广义遍历"的特化.
特别zipWithP是被称为binaryOp.
{-# LANGUAGE TypeApplications #-}
import Generics.OneLiner
main = print (binaryOp @Num (+) (1,2) (3,4) :: (Int, Integer))
| 归档时间: | 
 | 
| 查看次数: | 633 次 | 
| 最近记录: |