在Haskell中实现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和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来做。我在想结果应该是这样的:

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