是否有类似于Applicative
类型类的东西,但是应用程序的每一侧都有两个不同的函子?
即(<*>) :: (Functor f, Functor g) => f (a -> b) -> g a -> f b
答案 0 :(得分:5)
(在评论中来自@dfeuer的建议。)
有一个名为day convolution的构造,可让您在执行应用操作时保留两个函子之间的区别,并延迟将一个函子转换为另一个函子的时间。
Day
类型只是一对函数值,以及一个结合了它们各自结果的函数:
data Day f g a = forall b c. Day (f b) (g c) (b -> c -> a)
请注意,函子的实际返回值已存在。组合的返回值就是函数的返回值。
Day
优于组合应用函子的其他方式。与Sum
不同,该组合仍然适用。与Compose
不同,合成是“无偏的”,并且不施加嵌套顺序。与Product
不同,它使我们能够轻松地将应用操作与不同返回类型结合在一起,我们只需要提供合适的适配器函数即可。
例如,以下是两个Day ZipList (Vec Nat2) Char
值:
{-# LANGUAGE DataKinds #-}
import Data.Functor.Day -- from "kan-extensions"
import Data.Type.Nat -- from "fin"
import Data.Vec.Lazy -- from "vec"
import Control.Applicative
day1 :: Day ZipList (Vec Nat2) Char
day1 = Day (pure ()) ('b' ::: 'a' ::: VNil) (flip const)
day2 :: Day ZipList (Vec Nat2) Char
day2 = Day (ZipList "foo") (pure ()) const
({Nat2
来自fin包,用于参数化vec中的固定尺寸Vec
。)
我们可以将它们压缩在一起:
res :: Day ZipList (Vec Nat2) (Char,Char)
res = (,) <$> day1 <*> day2
然后将Vec
转换为ZipList
并折叠Day
:
res' :: ZipList (Char,Char)
res' = dap $ trans2 (ZipList . toList) res
ghci> res'
ZipList {getZipList = [('b','f'),('a','o')]}
可能的性能问题:当我们将一个函子提升到Day
时,另一个函子将得到一个虚拟的pure ()
值。但这在将Day
与(<*>)
组合在一起时是无谓的。通过将函子包装在变压器中的Lift
中,可以使工作更智能,从而在简单的“纯”情况下获得更快的操作。
答案 1 :(得分:2)
“序列类型”的一个一般概念是自由半身像。由于您正在研究多态序列类型,因此我们可以基于Traversable
。
class Semigroup1 t where
(<=>) :: t a -> t a -> t a
class Semigroup1 t => Monoid1 t where
mempty1 :: t a
请参阅下面的注释。
class (Traversable t, Monoid1 t) => Sequence t where
singleton :: a -> t a
该序列类型如何?效率很低。但是我们可以使用默认实现添加一堆方法来提高效率。以下是一些基本功能:
cons :: Sequence t => a -> t a -> t a
cons x xs = singleton x <=> xs
fromList
:: (Foldable f, Sequence t)
=> f a -> t a
fromList = foldr cons mempty1
uncons :: Sequence t => t a -> Maybe (a, t a)
uncons xs = case toList xs of
y:ys -> Just (y, fromList ys)
[] -> Nothing
使用这些工具,您可以将任意两个序列压缩成第三个序列。
zipApp :: (Foldable t, Foldable u, Sequence v) = t (a -> b) -> u a -> v b
zipApp fs xs = fromList $ zipWith ($) (toList fs) (toList xs)
对于边缘GHC,您可以使用QuantifiedConstraints
和RankNTypes
和ConstraintKinds
进行定义
type Semigroup1 t = forall a. Semigroup (t a)
type Monoid1 t = forall a. Monoid (t a)
通过这种方式进行操作,例如,
fromList = foldMap singleton
答案 2 :(得分:1)
根据您的评论,我认为您可能正在尝试构建:
import Data.Foldable
import Data.Traversable
foo :: (Traversable f, Foldable g) => f (a -> b) -> g a -> f b
foo f g = snd $ mapAccumR (\(a:as) fab -> (as, fab a)) (toList g) f
例如,这允许:
> import qualified Data.Vector as V
> foo [(+1),(+2),(+3)] (V.fromList [5,6,7])
[8,8,8]
>
答案 3 :(得分:0)
我不知道任何通用的我会写具体的版本,或者最多概括一下输入类型。以下是fromList
。Vector
的示例,忽略了Data.Vector.zip
已经存在。
import qualified Data.Vector as V
import Data.Vector (Vector)
import Data.Foldable
import GHC.Exts (IsList(fromList))
zipV1 :: Vector (a -> b) -> Vector a -> Vector b
zipV1 fs as = V.fromList (zipWith ($) (V.toList fs) (V.toList as))
zipV2 :: (Foldable f, Foldable g, IsList (f b)) => f (a -> b) -> g a -> f b
zipV2 fs as = fromList (zipWith ($) (toList fs) (toList as))
在第二个示例中,您可以使用IsList
代替Foldable
。