在Haskell中实现liftM2
作为练习,我一直在尝试使用函数ap和liftM来实现liftM2。这些功能定义为:在Haskell中实现liftM2,haskell,functional-programming,Haskell,Functional Programming,作为练习,我一直在尝试使用函数ap和liftM来实现liftM2。这些功能定义为: ap :: IO (a -> b) -> IO a -> IO b liftM :: (a -> b) -> IO a -> IO b liftM2 :: (a -> b -> c) -> IO a -> IO b -> IO c 我可以很容易地用do符号来做liftM2,但不知道如何只用ap和liftM来做。我在想结果应该是这样的: lift
ap :: IO (a -> b) -> IO a -> IO b
liftM :: (a -> b) -> IO a -> IO b
liftM2 :: (a -> b -> c) -> IO a -> IO b -> IO c
我可以很容易地用do符号来做liftM2,但不知道如何只用ap和liftM来做。我在想结果应该是这样的:
liftM2 f a b = liftM (_) (ap _ a)
我不知道如何处理f,它是(a->b->c),这样我就可以把a变成b,把b变成c。谢谢。总体格局正在转变
liftMn f a1 ... an
进入
检查类型:
f :: t1 -> t2 -> r
liftM f :: IO t1 -> IO (t2 -> r)
a1 :: IO t1
liftM f a1 :: IO (t2 -> r)
ap (liftM f a1) :: IO t2 -> IO r
a2 :: IO t2
ap (liftM f a1) a2 :: IO r
这里的关键思想是将
f::t1->t2->r
读作f::t1->(t2->r)
,这样liftM f::IO t1->IO(t2->r)
如下所示。注意IO
中的函数类型。然后,我们可以使用ap
在->
上“分发”IO
,这样我们就可以应用a2::IO t2我认为值得注意的是,您最初的猜测
liftM2 f a b = liftM (_) (ap _ a)
真的没那么远。但是ap
并不是该形状的正确起点。相反,考虑
pairIO :: IO a -> IO b -> IO (a, b)
pairIO m n = do
a <- m
b <- n
return (a, b)
GHC将告诉您它需要
_ :: (a, b) -> c
你应该能够很容易地填补这个空缺
这实际上反映了“应用函子”概念的一个常见替代公式:
该类的功能与标准应用类的功能相当
事实证明,由于Monoidal
关联法则,实际上可以以各种方式组合动作。这看起来像
xs `pair` (ys `pair` zs) = jigger <$> ((xs `pair` ys) `pair` z's)
where
jigger ((x, y), z) = (x, (y, z))
xs`pair`(ys`pair`zs)=跳汰机((xs`pair`ys)`pair`z's)
哪里
跳汰机((x,y,z)=(x,(y,z))
对于ap::IO(a->b)->ioa->iob
,我们两者都有
IO (a -> b) {- and -} IO (a -> b -> c)
IO a IO a
----------- ----------------
IO b IO (b -> c)
因此,我们可以通过
ap2 :: IO (a -> b -> c) -> IO a -> IO b -> IO c
ap2 mf mx my = ap mf mx `ap` my
IO (a -> b -> c)
IO a
----------------
IO (b -> c)
IO b
----------------
IO c
或者使用纯二进制函数
liftM2 :: (a -> b -> c) -> IO a -> IO b -> IO c
liftM2 f mx my = ap2 (return f) mx my
= ap (return f) mx `ap` my
= (ap . return) f mx `ap` my
什么是ap的类型。返回
> :t ap . return
ap . return :: Monad m => (a -> b) -> m a -> m b
为什么,这是liftM
的类型!(这里有一个更具体的(a->b->c)->IO a->IO(b->c)
)
切换liftM
和ap
的顺序如何?这项练习从何而来liftM*
和ap
几乎已经过时,现代版本是fmap
/liftA*
和
。感谢您明确指出关键思想。考虑$F$只处理一个参数作为返回另一个函数,这反过来又期望一个参数,这真的很有启发性。
IO (a -> b) {- and -} IO (a -> b -> c)
IO a IO a
----------- ----------------
IO b IO (b -> c)
ap2 :: IO (a -> b -> c) -> IO a -> IO b -> IO c
ap2 mf mx my = ap mf mx `ap` my
IO (a -> b -> c)
IO a
----------------
IO (b -> c)
IO b
----------------
IO c
liftM2 :: (a -> b -> c) -> IO a -> IO b -> IO c
liftM2 f mx my = ap2 (return f) mx my
= ap (return f) mx `ap` my
= (ap . return) f mx `ap` my
> :t ap . return
ap . return :: Monad m => (a -> b) -> m a -> m b
= liftM f mx `ap` my