也许 monad 和一个列表
Maybe monad and a list
好的,所以我正在尝试学习如何使用 monad,从 maybe 开始。我想出了一个例子,但我不知道如何以一种很好的方式应用它,所以我希望其他人可以:
我有一个包含一堆值的列表。根据这些值,我的函数应该 return 列表本身,或者什么都没有。换句话说,我想做一种过滤,但命中的结果是函数失败。
我能想到的唯一方法是使用过滤器,然后比较我返回的列表的大小为零。有没有更好的方法?
这看起来很适合 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
相同,以及 (<*>)
,与 (>>=)
但与之截然不同。对于 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
值,(>>=)
到达它的内部应用f
并产生一个结果,这取决于 mx
中的内容,结果将是 Just
值或 Nothing
。但是,在 mf <*> mx
中,如果 mf
和 mx
是 Just
值,您肯定会得到一个 Just
值,它将保存应用从 mf
到 mx
的值的函数。 (顺便说一句:如果mf
或mx
是Nothing
会怎样?)
traverse
涉及 Applicative
因为我在开头提到的值的组合(在您的示例中,将多个 Maybe a
值转换为 Maybe [a]
) 使用 (<*>)
完成。由于您的问题最初是关于 monad 的,因此值得注意的是可以使用 Monad
而不是 Applicative
来定义 traverse
。此变体的名称为 mapM
:
mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)
我们更喜欢 traverse
而不是 mapM
因为它更通用——如上所述,Applicative
是 Monad
的超类。
最后,您对这个 "a sort of filter" 的直觉很有道理。特别是,考虑 Maybe a
的一种方法是,当您选择布尔值并将类型 a
的值附加到 True
时,它就是您得到的结果。从那个有利的角度来看,(<*>)
作为这些奇怪的布尔值的 &&
,如果您碰巧提供其中的两个值,它会结合附加值(参见 of an implementation using any
). Once you get used to Traversable
, you might enjoy having a look at the Filterable
and Witherable
classes,这与这种关系有关在 Maybe
和 Bool
之间。
duplode 的回答很好,但我认为学习以更基本的方式在 monad 中操作也很有帮助。学习每个小的 monad-general 函数,并了解它们如何组合在一起解决特定问题可能是一个挑战。因此,这里有一个 DIY 解决方案,展示了如何使用 do 符号和递归,这些工具可以帮助您解决任何一元问题。
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
的相反实现:
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
结构是一样的,只有几个区别:当谓词 return 为真时你想做什么,以及你如何访问递归 returned 的值称呼。对于 remove
,returned 值是一个列表,因此您可以 let
-绑定它并对其进行约束。使用 forbid
,returned 值只是 maybe 一个列表,因此您需要使用 <-
绑定到该 monadic 值。如果 return 值为 Nothing,bind 将使计算短路并且 return Nothing;如果它只是一个列表,do
块将继续,并将一个值放在该列表的前面。然后你将它包装在一个 Just 中。
好的,所以我正在尝试学习如何使用 monad,从 maybe 开始。我想出了一个例子,但我不知道如何以一种很好的方式应用它,所以我希望其他人可以:
我有一个包含一堆值的列表。根据这些值,我的函数应该 return 列表本身,或者什么都没有。换句话说,我想做一种过滤,但命中的结果是函数失败。
我能想到的唯一方法是使用过滤器,然后比较我返回的列表的大小为零。有没有更好的方法?
这看起来很适合 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
相同,以及 (<*>)
,与 (>>=)
但与之截然不同。对于 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
值,(>>=)
到达它的内部应用f
并产生一个结果,这取决于 mx
中的内容,结果将是 Just
值或 Nothing
。但是,在 mf <*> mx
中,如果 mf
和 mx
是 Just
值,您肯定会得到一个 Just
值,它将保存应用从 mf
到 mx
的值的函数。 (顺便说一句:如果mf
或mx
是Nothing
会怎样?)
traverse
涉及 Applicative
因为我在开头提到的值的组合(在您的示例中,将多个 Maybe a
值转换为 Maybe [a]
) 使用 (<*>)
完成。由于您的问题最初是关于 monad 的,因此值得注意的是可以使用 Monad
而不是 Applicative
来定义 traverse
。此变体的名称为 mapM
:
mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)
我们更喜欢 traverse
而不是 mapM
因为它更通用——如上所述,Applicative
是 Monad
的超类。
最后,您对这个 "a sort of filter" 的直觉很有道理。特别是,考虑 Maybe a
的一种方法是,当您选择布尔值并将类型 a
的值附加到 True
时,它就是您得到的结果。从那个有利的角度来看,(<*>)
作为这些奇怪的布尔值的 &&
,如果您碰巧提供其中的两个值,它会结合附加值(参见 any
). Once you get used to Traversable
, you might enjoy having a look at the Filterable
and Witherable
classes,这与这种关系有关在 Maybe
和 Bool
之间。
duplode 的回答很好,但我认为学习以更基本的方式在 monad 中操作也很有帮助。学习每个小的 monad-general 函数,并了解它们如何组合在一起解决特定问题可能是一个挑战。因此,这里有一个 DIY 解决方案,展示了如何使用 do 符号和递归,这些工具可以帮助您解决任何一元问题。
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
的相反实现:
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
结构是一样的,只有几个区别:当谓词 return 为真时你想做什么,以及你如何访问递归 returned 的值称呼。对于 remove
,returned 值是一个列表,因此您可以 let
-绑定它并对其进行约束。使用 forbid
,returned 值只是 maybe 一个列表,因此您需要使用 <-
绑定到该 monadic 值。如果 return 值为 Nothing,bind 将使计算短路并且 return Nothing;如果它只是一个列表,do
块将继续,并将一个值放在该列表的前面。然后你将它包装在一个 Just 中。