For an exercise, I've been trying to implement liftM2 using just the functions ap and liftM. The functions are defined as:
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
I can easily do liftM2 using do notation but not sure how to do it using just ap and liftM. I was thinking of having the result be something that looks like this:
liftM2 f a b = liftM (_) (ap _ a)
I'm confused on how to mess with f, which is (a -> b -> c) such that I can just turn a to b and b to c. Thank you.
The general pattern is transforming
liftMn f a1 ... an
into
f <$> a1 <*> ... <*> an
-- i.e., more precisely
(... ((f <$> a1) <*> a2) ... <*> an)
where <$> is liftM (AKA fmap) and <*> is ap.
Hence, for n=2 we get
(f `liftM` a1) `ap` a2
-- i.e.
ap (liftM f a1) a2
Checking the types:
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
The key idea here is to read f :: t1 -> t2 -> r as f :: t1 -> (t2 -> r) so that liftM f :: IO t1 -> IO (t2 -> r) follows. Note the function type inside the IO. We can then "distribute" IO over -> using ap, so that we can apply a2 :: IO t2.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With