"zipping" 等长元组是否有 Haskell 透镜函数?
Is there a Haskell lens function for "zipping" same-length tuples?
我希望能够使用函数组合两个相同长度的元组,类似于 base
中的 zipWith
函数。
比如对于长度为3元组的情况:
zipTupleWith f (a0,a1,a2) (b0,b1,b2) = (f a0 b0, f a1 b1, f a2 b2)
尽管我想要一个适用于任何长度的函数。
我使用 lens
包创建了一个函数 zipTupleWith
:
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
。此限制是因为在参数 a
.
上使用了 partsOf
函数
我对我的解决方案不满意的原因有 3 个:
- 我希望能够使用
a -> b -> c
类型的函数,允许 zipTuple = zipTupleWith (,)
.
- 上面的实现不会捕获由传入的不同长度的元组引起的错误(
zipTupleWith (+) (1,2,3) (100,100) = (101, 102, 3)
- 我希望这是一个编译错误)。
- 它创建一个中间列表 (
b ^.. each
)。
那么,有没有办法使用光学来做到这一点?
我看到 tuple-th
包可以做到这一点,但我宁愿避免为此添加另一个依赖项,模板 Haskell 似乎对我正在做的事情来说太过分了。
我知道您要求的是基于镜头的方法,但如果您只有小元组,则可以使用类型 class 实现您想要的,而不会有太多麻烦。考虑例如:
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,a) a where
type TupOf (a,a,a) b = (b,b,b)
zipTuple f (a1,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 中提取所有 a
的表达式 s ^.. each
隐含的,而 Each t v b c
隐含在 t & unsafePartsOf each %~ ...
对于某些 ... :: [b] -> [c]
。但是添加其他不必要的约束 Each t s b a
通过断言 IF 在 t
中的每个 b
被替换为 a
,结果将是 s
.
请注意 lens
在这里没有做任何神奇的事情。有一个 Each
实例用于一堆不同大小的元组,类型 class 中有足够的信息来欺骗它以非常丑陋的迂回方式定义 zipTupleWith
。
根据@DDub 的回答,直接定义您需要的类型 class 会更直接。
我希望能够使用函数组合两个相同长度的元组,类似于 base
中的 zipWith
函数。
比如对于长度为3元组的情况:
zipTupleWith f (a0,a1,a2) (b0,b1,b2) = (f a0 b0, f a1 b1, f a2 b2)
尽管我想要一个适用于任何长度的函数。
我使用 lens
包创建了一个函数 zipTupleWith
:
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
。此限制是因为在参数 a
.
partsOf
函数
我对我的解决方案不满意的原因有 3 个:
- 我希望能够使用
a -> b -> c
类型的函数,允许zipTuple = zipTupleWith (,)
. - 上面的实现不会捕获由传入的不同长度的元组引起的错误(
zipTupleWith (+) (1,2,3) (100,100) = (101, 102, 3)
- 我希望这是一个编译错误)。 - 它创建一个中间列表 (
b ^.. each
)。
那么,有没有办法使用光学来做到这一点?
我看到 tuple-th
包可以做到这一点,但我宁愿避免为此添加另一个依赖项,模板 Haskell 似乎对我正在做的事情来说太过分了。
我知道您要求的是基于镜头的方法,但如果您只有小元组,则可以使用类型 class 实现您想要的,而不会有太多麻烦。考虑例如:
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,a) a where
type TupOf (a,a,a) b = (b,b,b)
zipTuple f (a1,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 中提取所有 a
的表达式 s ^.. each
隐含的,而 Each t v b c
隐含在 t & unsafePartsOf each %~ ...
对于某些 ... :: [b] -> [c]
。但是添加其他不必要的约束 Each t s b a
通过断言 IF 在 t
中的每个 b
被替换为 a
,结果将是 s
.
请注意 lens
在这里没有做任何神奇的事情。有一个 Each
实例用于一堆不同大小的元组,类型 class 中有足够的信息来欺骗它以非常丑陋的迂回方式定义 zipTupleWith
。
根据@DDub 的回答,直接定义您需要的类型 class 会更直接。