基于两个不同函子的适用类型类

时间:2019-04-17 12:35:07

标签: haskell functor applicative

是否有类似于Applicative类型类的东西,但是应用程序的每一侧都有两个不同的函子?

(<*>) :: (Functor f, Functor g) => f (a -> b) -> g a -> f b

4 个答案:

答案 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')]}

使用daptrans2函数。

可能的性能问题:当我们将一个函子提升到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版本的说明

对于边缘GHC,您可以使用QuantifiedConstraintsRankNTypesConstraintKinds进行定义

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