亚历克表示,bimap
有利于对。如果你想更一般地处理元组,一种选择是使用一个类。下面的灵感来自于lens
(特别是Control.Lens.Tuple
)的一些想法,但我不认为它与其中的任何想法完全相同。
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Tuplish where
import Data.Functor.Identity (Identity (..))
import Data.Profunctor.Unsafe ((#.), (.#))
class Tuply s t a b | s -> a, t -> b, s b -> t, t a -> s where
ttraverse :: Applicative f => (a -> f b) -> s -> f t
mapTuple :: Tuply s t a b => (a -> b) -> (s -> t)
mapTuple = (runIdentity .) #. ttraverse .# (Identity .)
instance (a1 ~ a, a2 ~ a, b1 ~ b, b2 ~ b)
=> Tuply (a1, a2) (b1, b2) a b where
ttraverse f (x,y) = (,) <$> f x <*> f y
instance (a1 ~ a, a2 ~ a, a3 ~ a, b1 ~ b, b2 ~ b, b3 ~ b)
=> Tuply (a1,a2,a3) (b1,b2,b3) a b where
ttraverse f (x,y,z) = (,,) <$> f x <*> f y <*> f z
instance (a1 ~ a, a2 ~ a, a3 ~ a, a4 ~ a, b1 ~ b, b2 ~ b, b3 ~ b, b4 ~ b)
=> Tuply (a1,a2,a3,a4) (b1,b2,b3,b4) a b where
ttraverse f (x,y,z,w) = (,,,) <$> f x <*> f y <*> f z <*> f w
instance (a1 ~ a, a2 ~ a, a3 ~ a, a4 ~ a, a5 ~ a,
b1 ~ b, b2 ~ b, b3 ~ b, b4 ~ b, b5 ~ b)
=> Tuply (a1,a2,a3,a4,a5) (b1,b2,b3,b4,b5) a b where
ttraverse f (x,y,z,w,u) = (,,,,) <$> f x <*> f y <*> f z <*> f w <*> f u
instance (a1 ~ a, a2 ~ a, a3 ~ a, a4 ~ a, a5 ~ a, a6 ~ a,
b1 ~ b, b2 ~ b, b3 ~ b, b4 ~ b, b5 ~ b, b6 ~ b)
=> Tuply (a1,a2,a3,a4,a5,a6) (b1,b2,b3,b4,b5,b6) a b where
ttraverse f (x,y,z,w,u,v) = (,,,,,) <$> f x <*> f y <*> f z <*> f w <*> f u <*> f v
“奇怪!” - 它实际上是唯一可能的实现,但是在数据构造函数上使用的元组类型的语法隐藏了它。更直接的将是:'数据元组a b =元组a b'; '实例Functor(Tuple a)'...所以元组上'fmap'的具体类型签名是(为了清晰起见,有冗余的元素):'fmap ::(b - > c) - >(Tuple a)b - > (元组a)c'其中'元组a'是你的'f'。 – jberryman
当我第一次遇到它时,我将验证这个“最后的类型参数”业务确实很奇怪。 Haskell对参数的顺序有些敏感,特别是类型参数,这与基本上所有其他语言并不重要的语言完全不同。 – luqui