在Haskell中实现liftM2

时间:2018-11-04 22:02:54

标签: haskell functional-programming

为了练习,我一直在尝试仅使用功能ap和liftM来实现liftM2。这些功能定义为:

<div class="swiper-container">
 <div class="swiper-slides"></div>
</div>
<div class="swiper-button-prev-unique"></div>
<div class="swiper-button-next-unique"></div>

let carousel = new Swiper('.swiper-container', {
  navigation: {
    nextEl: '.swiper-button-next-unique',
    prevEl: '.swiper-button-prev-unique'
  }
});

我可以轻松地使用do表示法来完成liftM2,但是不确定如何仅使用ap和liftM来完成。我当时想让结果看起来像这样:

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

我对如何弄乱f感到困惑,f是(a-> b-> c),这样我就可以将a转到b并将b转到c。谢谢。

3 个答案:

答案 0 :(得分:6)

一般模式正在转变

liftMn f a1 ... an

进入

f <$> a1 <*> ... <*> an
-- i.e., more precisely
(... ((f <$> a1) <*> a2) ... <*> an)

其中<$>liftM(又名fmap),而<*>ap

因此,对于n=2,我们得到

(f `liftM` a1) `ap` a2
-- i.e.
ap (liftM f a1) a2

检查类型:

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内的函数类型。然后,我们可以使用IO->上“分发” ap,以便我们可以应用a2 :: IO t2

答案 1 :(得分:0)

我认为值得注意的是您的最初猜测,

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)

现在,您可以写

liftM2 :: (a -> b -> c) -> IO a -> IO b -> IO c
liftM2 f m n = liftM _ (pairIO m n)

GHC会告诉您它的需要

_ :: (a, b) -> c

您应该能够非常轻松地完成此操作。

这实际上反映了“应用函子”概念的常见替代表达:

class Functor f => Monoidal f where
  pur :: a -> f a
  pair :: f a -> f b -> f (a, b)

该类的能力与标准Applicative类相同。


事实证明,由于Monoidal关联律,您实际上可以以各种方式组合动作。看起来像

xs `pair` (ys `pair` zs) = jigger <$> ((xs `pair` ys) `pair` z's)
   where
     jigger ((x, y), z) = (x, (y, z))

答案 2 :(得分:0)

有了ap :: IO (a -> b) -> IO a -> IO b,我们都拥有

  IO (a -> b)         {- and -}       IO (a -> b -> c)
  IO  a                               IO  a
  -----------                         ----------------
  IO       b                          IO      (b -> c)

因此我们可以通过IO二进制函数将两个IO值组合在一起

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 . return是什么类型?

> :t ap . return
ap . return :: Monad m => (a -> b) -> m a -> m b

为什么是liftM的类型! (此处为更具体的(a -> b -> c) -> IO a -> IO (b -> c)

              = liftM f mx `ap` my