好吧,所以我尝试学习使用monad的方法,也许是从开始。我举了一个例子,我不知道如何以一种很好的方式将其应用到它,所以我希望其他人可以:
我有一个包含一堆值的列表。根据这些值,我的函数应返回列表本身或Nothing。换句话说,我想做一个过滤器,但是结果是函数失败了。
我能想到的唯一方法是使用过滤器,然后将列表的大小比较为零。有更好的方法吗?
答案 0 :(得分:2)
这看起来很适合traverse
:
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
这有点麻烦,所以让我们通过列表和Maybe
将其专用于您的用例:
GHCi> :set -XTypeApplications
GHCi> :t traverse @[] @Maybe
traverse @[] @Maybe :: (a -> Maybe b) -> [a] -> Maybe [b]
它的工作原理是:您为它提供一个a -> Maybe b
函数,该函数像fmap
一样应用于列表的所有元素。扭曲之处在于,Maybe b
值然后以某种方式组合,如果没有Nothing
,则仅给您修改后的列表。否则,总结果为Nothing
。就像手套一样满足您的要求:
noneOrNothing :: (a -> Bool) -> [a] -> Maybe [a]
noneOrNothing p = traverse (\x -> if p x then Nothing else Just x)
({allOrNothing
本来应该是更谐音的名称,但随后我将不得不根据您的描述进行测试。)
关于Traversable
和Applicative
类,我们可能要讨论很多事情。现在,我将进一步介绍Applicative
,以防您尚未见到它。 Applicative
是Monad
的超类,具有两个基本方法:pure
(与return
相同)和(<*>)
(与{ {1}},但与之极为不同。对于(>>=)
示例...
Maybe
...我们可以这样描述差异:在GHCi> :t (>>=) @Maybe
(>>=) @Maybe :: Maybe a -> (a -> Maybe b) -> Maybe b
GHCi> :t (<*>) @Maybe
(<*>) @Maybe :: Maybe (a -> b) -> Maybe a -> Maybe b
中,如果mx >>= f
是一个mx
值,则Just
会到达其中以应用{{ 1}}并产生一个结果,该结果取决于(>>=)
内的内容,结果将是一个f
值或一个mx
。但是,在Just
中,如果Nothing
和mf <*> mx
是mf
值,则可以保证获得一个mx
值,该值将保留应用函数从Just
到Just
的值。 (顺便说一句:如果mf
或mx
为mf
会发生什么?)
mx
涉及Nothing
,因为我在开头提到的值组合(在您的示例中,将多个traverse
值转换为Applicative
)是使用Maybe a
完成。由于您的问题最初是关于单子的,因此值得注意的是可以使用Maybe [a]
而不是(<*>)
来定义traverse
。此变体的名称为Monad
:
Applicative
我们更喜欢mapM
而不是mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)
,因为它更笼统-如上所述,traverse
是mapM
的超类。
最后,您对这是“一种过滤器”的直觉很有道理。特别地,考虑Applicative
的一种方法是,当您选择布尔值并将Monad
类型的值附加到Maybe a
时得到的结果。从这个有利的角度来看,a
就像这些奇怪的布尔值的True
一样,如果您恰巧提供了两个附加值(请参见使用{{ 3}})。习惯了(<*>)
之后,您可能会喜欢看DarthFennec's suggestion,它在&&
和Traversable
之间具有这种关系。
答案 1 :(得分:1)
duplode的答案是一个很好的答案,但我认为以更基本的方式学习在monad中操作也很有帮助。学习每个monad通用功能,并了解如何将它们组合在一起以解决特定问题可能是一个挑战。因此,这里有一个DIY解决方案,展示了如何使用符号和递归,这些工具可以帮助您解决任何单子问题。
var filtered = src
.GroupBy(s => s.Name)
.SelectMany(g => { // for each name group
var skipCount = g.Where(r => r.Status == 0).Count(); // count the 0 status
return g.Where(r => r.Status == 1).OrderBy(r => r.Created).Skip(skipCount); // return the 1 status without earliest matched to 0 status
})
.OrderBy(s => s.Id);
将此与forbid :: (a -> Bool) -> [a] -> Maybe [a]
forbid _ [] = Just []
forbid p (x:xs) = if p x
then Nothing
else do
remainder <- forbid p xs
Just (x : remainder)
的实现(与remove
相反)进行比较:
filter
结构相同,只是有几个区别:谓词返回true时要执行的操作,以及如何访问递归调用返回的值。对于remove :: (a -> Bool) -> [a] -> [a]
remove _ [] = []
remove p (x:xs) = if p x
then remove p xs
else
let remainder = remove p xs
in x : remainder
,返回值是一个列表,因此您可以remove
对其进行绑定并使用它。使用let
时,返回的值可能只是 列表,因此您需要使用forbid
绑定到该单价值。如果返回值为Nothing,bind将使计算短路并返回Nothing;否则,返回false。如果它只是一个列表,则<-
块将继续,并将一个值限制在该列表的前面。然后将其包装回Just中。