是否有用于“压缩”相同长度元组的 Haskell 镜头功能?

问题描述

我希望能够使用一个函数组合两个相同长度的元组,类似于 zipwith 中的 base 函数。 例如,对于长度为 3 元组的情况:

zipTupleWith f (a0,a1,a2) (b0,b1,b2) = (f a0 b0,f a1 b1,f a2 b2)

虽然我想要一个适用于任何长度的函数

我使用 zipTupleWith 包创建了一个函数 lens

zipTupleWith :: (Each s1 t1 a a,Each s2 s2 b b) => (a -> b -> a) -> s1 -> s2 -> t1
zipTupleWith f a b = a & partsOf each %~ flip (zipwith f) (b ^.. each)

这可以zipwith两个元组,只要函数的类型是a -> b -> a。此限制是因为 partsOf 函数用于参数 a

我对我的解决方案不满意有 3 个原因:

  1. 我希望能够使用 a -> b -> c 类型的函数,允许 zipTuple = zipTupleWith (,) 之类的东西。
  2. 上述实现不会捕获由传入不同长度的元组引起的错误zipTupleWith (+) (1,2,3) (100,100) = (101,102,3) - 我希望这是一个编译错误)。
  3. 它会创建一个中间列表 (b ^.. each)。

那么,有没有办法使用光学来做到这一点? 我看到 tuple-th 包可以做到这一点,但我宁愿避免为此添加一个依赖项,而 Template Haskell 似乎对我正在做的事情来说太过分了。

解决方法

我知道您要求采用基于镜头的方法,但是如果您只有小元组,则可以使用类型类实现您想要的内容而不会遇到太多麻烦。考虑例如:

class ZipTuple as a where
  type TupOf as x :: *
  zipTuple :: (a -> b -> c) -> as -> TupOf as b -> TupOf as c

instance ZipTuple (a,a) a where
  type TupOf (a,a) b = (b,b)
  zipTuple f (a1,a2) (b1,b2) = (f a1 b1,f a2 b2)

instance ZipTuple (a,a,b,a2,a3) (b1,b2,b3) = (f a1 b1,f a2 b2,f a3 b3)

可能有更优雅的写法,但模式很简单。为您想要的任何长度的元组添加实例应该很容易。


如果你想要任意长的元组但不想要模板haskell,还有泛型路线。这是一个基于通用表示的形状压缩的解决方案:

import GHC.Generics

class TupleZipG fa fb a b c | fa -> a,fb -> b where
  type Out fa fb a b c :: (* -> *)
  tupleZipG :: (a -> b -> c) -> fa x -> fb x -> Out fa fb a b c x

instance (TupleZipG l1 l2 a b c,TupleZipG r1 r2 a b c) => TupleZipG (l1 :*: r1) (l2 :*: r2) a b c where
  type Out (l1 :*: r1) (l2 :*: r2) a b c = Out l1 l2 a b c :*: Out r1 r2 a b c
  tupleZipG f (l1 :*: r1) (l2 :*: r2) = tupleZipG f l1 l2 :*: tupleZipG f r1 r2

instance TupleZipG (S1 m (Rec0 a)) (S1 m' (Rec0 b)) a b c where
  type Out (S1 m (Rec0 a)) (S1 m' (Rec0 b)) a b c = S1 m (Rec0 c)
  tupleZipG f (M1 (K1 a)) (M1 (K1 b)) = M1 $ K1 $ f a b

instance TupleZipG fa fb a b c => TupleZipG (D1 m fa) (D1 m' fb) a b c where
  type Out (D1 m fa) (D1 m' fb) a b c = D1 m (Out fa fb a b c)
  tupleZipG f (M1 a) (M1 b) = M1 $ tupleZipG f a b

instance TupleZipG fa fb a b c => TupleZipG (C1 m fa) (C1 m' fb) a b c where
  type Out (C1 m fa) (C1 m' fb) a b c = C1 m (Out fa fb a b c)
  tupleZipG f (M1 a) (M1 b) = M1 $ tupleZipG f a b

tupleZip
  :: (TupleZipG (Rep as) (Rep bs) a b c,Generic cs,Generic as,Generic bs,Out (Rep as) (Rep bs) a b c ~ Rep cs) =>
     (a -> b -> c) -> as -> bs -> cs
tupleZip f t1 t2 = to $ tupleZipG f (from t1) (from t2)

警告:这种通用方法的类型推断不是很好。

,

看起来你可以这样做:

zipTupleWith :: (Each s s a a,Each t v b c,Each t s b a)
  => (a -> b -> c) -> s -> t -> v
zipTupleWith f s t = t & unsafePartsOf each %~ zipWith f (s ^.. each)

给予:

> zipTupleWith replicate (1,2,3) ('a','b','c')
("a","bb","ccc")
> zipTupleWith (+) (1,2) (3,4,5)
-- type error

这里的诀窍是“额外”约束Each t s b a。其他两个约束由 each 的两种用法隐含——基本上,Each s s a a 由从 s 中提取所有 s ^.. each 的表达式 a 隐含,而 {对于某些 Each t v b ct & unsafePartsOf each %~ ... 隐含 {1}}。但是添加其他不必要的约束 ... :: [b] -> [c] 通过断言 IF Each t s b a 中的每个 b 被替换为 {{1} },结果将是 t

请注意,a 在这里没有做任何神奇的事情。有一个 s 实例用于一堆不同大小的元组,并且类型类中有足够的信息来诱使它以一种非常丑陋、迂回的方式定义 lens

根据@DDub 的回答,直接定义您需要的类型类会更直接。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...