基于两个不同函子的应用类型类
Applicative typeclass based on two different functors
是否有类似于 Applicative
类型的东西 class,但是应用程序的每一侧都有两个不同的仿函数?
即(<*>) :: (Functor f, Functor g) => f (a -> b) -> g a -> f b
根据您的评论,我认为您可能试图构建:
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]
>
我不知道有什么通用的 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
。
"sequence type" 的一个一般概念是自由幺半群。由于您正在查看多态序列类型,我们可以在 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,您可以使用 QuantifiedConstraints
和 RankNTypes
和 ConstraintKinds
并定义
type Semigroup1 t = forall a. Semigroup (t a)
type Monoid1 t = forall a. Monoid (t a)
这样做会让你写,例如,
fromList = foldMap singleton
(根据@dfeuer 在评论中的建议。)
有一种叫做 day convolution 的结构可以让你在执行应用操作时保留两个函子之间的区别,并延迟将一个函子转换为另一个函子的时间。
Day
类型只是一对函子值,以及一个组合它们各自结果的函数:
data Day f g a = forall b c. Day (f b) (g c) (b -> c -> a)
注意函子的实际 return 值是存在的; return 组合的值是函数的值。
Day
比其他组合应用函子的方法有优势。与Sum
, the composition is still applicative. Unlike Compose
, the composition is "unbiased" and doesn't impose a nesting order. Unlike Product
不同的是,它让我们可以轻松地将applicative action与不同的 return类型结合起来,我们只需要提供一个合适的适配器函数即可。
例如,这里有两个 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
is from the fin package, it is used to parameterize a fixed-size Vec
from 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
中来更智能地工作,以便为简单的 "pure" 案例获得更快的操作。
是否有类似于 Applicative
类型的东西 class,但是应用程序的每一侧都有两个不同的仿函数?
即(<*>) :: (Functor f, Functor g) => f (a -> b) -> g a -> f b
根据您的评论,我认为您可能试图构建:
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]
>
我不知道有什么通用的 我会写一个具体的版本,或者最多概括一下输入类型。以下是 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
。
"sequence type" 的一个一般概念是自由幺半群。由于您正在查看多态序列类型,我们可以在 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,您可以使用 QuantifiedConstraints
和 RankNTypes
和 ConstraintKinds
并定义
type Semigroup1 t = forall a. Semigroup (t a)
type Monoid1 t = forall a. Monoid (t a)
这样做会让你写,例如,
fromList = foldMap singleton
(根据@dfeuer 在评论中的建议。)
有一种叫做 day convolution 的结构可以让你在执行应用操作时保留两个函子之间的区别,并延迟将一个函子转换为另一个函子的时间。
Day
类型只是一对函子值,以及一个组合它们各自结果的函数:
data Day f g a = forall b c. Day (f b) (g c) (b -> c -> a)
注意函子的实际 return 值是存在的; return 组合的值是函数的值。
Day
比其他组合应用函子的方法有优势。与Sum
, the composition is still applicative. Unlike Compose
, the composition is "unbiased" and doesn't impose a nesting order. Unlike Product
不同的是,它让我们可以轻松地将applicative action与不同的 return类型结合起来,我们只需要提供一个合适的适配器函数即可。
例如,这里有两个 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
is from the fin package, it is used to parameterize a fixed-size Vec
from 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
中来更智能地工作,以便为简单的 "pure" 案例获得更快的操作。