Why are this two equivalents?

蹲街弑〆低调 提交于 2019-12-24 07:26:23

问题


I don't quite understand why given two list of lists xss :: [[a]] and yss :: [[a]]

liftA2 (++) xss yss

is equivalent to

[xs ++ ys | xs <- xss, ys <- yss]

回答1:


The reason is right here in the source code.

instance Applicative [] where
    pure x    = [x]
    fs <*> xs = [f x | f <- fs, x <- xs]
    liftA2 f xs ys = [f x y | x <- xs, y <- ys]

The liftA2 definition is an optimization, and we could also do it manually with the default definition of liftA2:

liftA2 f x y = f <$> x <*> y

So

liftA2 (++) xs ys
   = (++) <$> xs <*> ys
   = fmap (++) xs <*> ys                  -- definition of <$> 
   = [ f y | f <- fmap (++) xs, y <- ys ] -- definition of <*> above
   = [ (++) x y | x <- xs, y <- ys ]      -- some law about fmap/bind
   = [ x ++ y | x <- xs, y <- ys ]

There you have it.


"Some law about fmap/bind" is this one:

fmap f x >>= t = x >>= t . f

which applies if you understand how list comprehensions are desugared. The proof is:

fmap f x >>= t
    = x >>= pure . f >>= t             -- fmap = liftM coherence
    = x >>= (\y -> pure (f y) >>= t)   -- definition of (.)
    = x >>= (\y -> t (f y))            -- left unit monad law
    = x >>= t . f                      -- definition of (.)



回答2:


This is exactly the reason why i don't like using liftA<2..n> type of functions. They are an abstraction over the monad abstraction. It's just so because applicative is introduced after monads just to simplify to context of monads those contain functional values (functions).

Basically liftA2 (++) xs ys is (++) <$> xs <*> ys which makes more sense since it involves functor operator fmap in it's inline form <$>. Once you undertand the mechanics of the latter liftA2 starts to make sense.

fmap just applies the (++) function to the elements of the xs list (assume that xs is [[1,2],[3,4]]) and turns it into an applicative list (a list that contains functions) such as;

[([1,2] ++), ([3,4] ++)] :: Num a => [[a] -> [a]]

and the applicative operator <*> is now eligible to apply these functions in our list to another list which contains some other lists such as say, [[1,2],[3,4]].

At this very moment we have to know how exactly lists are handled monadically. Lists are undeterministic data types. So every single element of the first list has to be applied to the every single element of the second list. So

[([1,2] ++), ([3,4] ++)] <*> [[1,2],[3,4]]

turns out to be

[[1,2,1,2],[1,2,3,4],[3,4,1,2],[3,4,3,4]]

All in all liftA2 (++) just lifts the simple (++) function to the list monad. Simply saying, concatenate inner lists with each other monadically.

Having said that the list comprehensions version of this is a joke in Haskell. It is redundant and should be avoided in my honest opinion. It just takes a whole monad abstraction down to list level only whereas monadical approaches hold for all data types according to their appropriate monad instances.



来源:https://stackoverflow.com/questions/58719726/why-are-this-two-equivalents

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!