也许单子和一个清单

时间:2018-11-08 23:57:59

标签: haskell monads

好吧,所以我尝试学习使用monad的方法,也许是从开始。我举了一个例子,我不知道如何以一种很好的方式将其应用到它,所以我希望其他人可以:

我有一个包含一堆值的列表。根据这些值,我的函数应返回列表本身或Nothing。换句话说,我想做一个过滤器,但是结果是函数失败了。

我能想到的唯一方法是使用过滤器,然后将列表的大小比较为零。有更好的方法吗?

2 个答案:

答案 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本来应该是更谐音的名称,但随后我将不得不根据您的描述进行测试。)

关于TraversableApplicative类,我们可能要讨论很多事情。现在,我将进一步介绍Applicative,以防您尚未见到它。 ApplicativeMonad的超类,具有两个基本方法: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中,如果Nothingmf <*> mxmf值,则可以保证获得一个mx值,该值将保留应用函数从JustJust的值。 (顺便说一句:如果mfmxmf会发生什么?)

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) ,因为它更笼统-如上所述,traversemapM的超类。

最后,您对这是“一种过滤器”的直觉很有道理。特别地,考虑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中。