我不太明白为什么要给出两个列表xss :: [[a]]
和yss :: [[a]]
liftA2 (++) xss yss
等同于
[xs ++ ys | xs <- xss,ys <- yss]
原因是源代码中的right here。
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]
liftA2
定义是一种优化,我们也可以使用默认的liftA2
手动完成它:
liftA2 f x y = f <$> x <*> y
所以
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 ]
那里有。
“关于fmap / bind的一些法律”是这样的:
fmap f x >>= t = x >>= t . f
,如果您了解如何理解列表推导,则适用。证明是:
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 (.),
这正是我不喜欢使用liftA<2..n>
类型的函数的原因。它们是对monad抽象的抽象。之所以如此,是因为在Monad之后引入了applicative,只是为了简化包含功能值(函数)的Monad的上下文。
基本上liftA2 (++) xs ys
是(++) <$> xs <*> ys
,因为它涉及内联形式fmap
的函子运算符<$>
,因此更有意义。一旦您了解了后一种liftA2
的机制,就可以理解了。
fmap
仅将(++)
函数应用于xs
列表的元素(假设xs
是[[1,2],[3,4]]
)并将其转换为适用列表(包含功能的列表),例如;
[([1,2] ++),([3,4] ++)] :: Num a => [[a] -> [a]]
并且应用运算符<*>
现在可以将我们列表中的这些功能应用于另一个包含其他列表(例如[[1,4]]
)的列表。
此刻,我们必须了解如何单例准确地处理列表。列表是不确定的数据类型。因此,必须将第一个列表的每个元素应用于第二个列表的每个元素。所以
[([1,4] ++)] <*> [[1,4]]
原来是
[[1,2,1,[1,3,4],4,4]]
总之liftA2 (++)
仅将简单的(++)
函数提升到列表monad。简而言之,将内部列表彼此串联。
已经说过,列表理解版本是Haskell的一个笑话。我认为这是多余的,应避免使用。仅需将整个monad抽象降低到列表级别,而monadical方法则根据其适当的monad实例适用于所有数据类型。