fmap.fmap
允许我们将“两层深度”变成一个仿函数:
fmap.fmap :: (a -> b) -> f (g a) -> f (g b)
这也适用于应用函子吗?假设我想通过使用它们的应用属性来组合Just (+5)
和[1,2,3]
。我可以想出一个明显的方法来做到这一点,但对我来说这似乎并不重要。
(<*>).(<*>)
没有确定的类型签名:
((<*>).(<*>)) :: (a1 -> a2 -> b) -> ((a1 -> a2) -> a1) -> (a1 -> a2) -> b
-- where I would expect something like:
-- ((<*>).(<*>)) :: f (g (a -> b)) -> f (g a) -> f (g b)
是否有可能以这种方式组成Just (+5)
和[1,2,3]
?
编辑:
第一步是采用以下两种方法:
pure $ Just (+5)
和fmap pure [1,2,3]
,或fmap pure (Just (+5)
和pure [1,2,3]
但我还是不知道怎么写这些......
编辑:
有一个通用的方法来组成一个函数f (g (a -> b)
和f (g a)
会很好,我不只是在寻找上述情况的解决方案,它只是作为这种函数的一个示例输入。基本上我想要一个功能:
(<***>) :: f (g (a -> b)) -> f (g a) -> f (g b)
liftA2
具有与fmap
类似的成分属性。
liftA2 f :: f a -> f b -> f c
(liftA2 . liftA2) f :: g (f a) -> g (f b) -> g (f c)
所以你可以写
(liftA2 . liftA2) ($) (pure (Just (+5))) (fmap pure [1,2,3]) :: [Maybe Integer]
即,(<***>) = (liftA2 . liftA2) ($)
。 (很像(<*>) = liftA2 ($)
)
另一种看待它的方法是,应用函子的组合是一个应用函子,这是由Data.Functor.Compose
制作的具体:
{-# LANGUAGE ScopedTypeVariables, PartialTypeSignatures #-}
import Data.Functor.Compose
import Data.Coerce
(<***>) :: forall f g a b. (Applicative f, Applicative g)
=> f (g (a -> b)) -> f (g a) -> f (g b)
(<***>) = coerce ((<*>) :: Compose f g (a -> b) -> _)
与coerce
的关键是要表明(<***>)
是正确类型的适用(<*>)
;我们也可以手动解包
f <***> x = getCompose $ Compose f <*> Compose x
我们有一个f (g (a->b))
。要从g a -> g b
获得g (a->b)
,我们只需要<*>
,但g (a->b)
包裹在f
。幸运的是f
是一个Functor,所以我们可以fmap
。
Prelude> :t fmap (<*>)
fmap (<*>)
:: (Functor f1, Applicative f) =>
f1 (f (a -> b)) -> f1 (f a -> f b)
Prelude>
那更好,我们现在有一个包含在Functor中的函数。如果这个Functor恰好是一个Applicative,我们可以通过它来应用<*>
。
Prelude> :t (<*>) . fmap (<*>)
(<*>) . fmap (<*>)
:: (Applicative f, Applicative f1) =>
f1 (f (a -> b)) -> f1 (f a) -> f1 (f b)
Prelude>
正是医生所要求的。
Prelude> let (<***>) = (<*>) . fmap (<*>)
Prelude> [Just (+2), Just (*3), Nothing] <***> [Just 7, Just 42, Nothing]
[Just 9,Just 44,Nothing,Just 21,Just 126,Nothing,Nothing,Nothing,Nothing]
Prelude>