是否可以在返回类型不是该单子的函数中为单子写do
表示法?
我有一个执行大多数代码逻辑的主要功能,另一个功能是在中间进行一些计算。补充功能可能会失败,这就是为什么它返回Maybe
值的原因。我正在寻找对主函数中的返回值使用do
表示法。举一个通用的例子:
-- does some computation to two Ints which might fail
compute :: Int -> Int -> Maybe Int
-- actual logic
main :: Int -> Int -> Int
main x y = do
first <- compute x y
second <- compute (x+2) (y+2)
third <- compute (x+4) (y+4)
-- does some Int calculation to first, second and third
我打算让first
,second
和third
具有从Int
上下文中取出的实际Maybe
值,但是上面的方式使Haskell抱怨无法将Maybe Int
与Int
的类型进行匹配。
有没有办法做到这一点?还是我朝着错误的方向前进?
请原谅我是否误用了某些术语,这对Haskell还是陌生的,我仍然想尽一切办法。
编辑
main
必须返回Int
,而不能包装在Maybe
中,因为代码的另一部分使用main
作为{{1} }。单个Int
的结果可能会失败,但它们应该在compute
中共同通过(即至少一个会通过),而我正在寻找的是使用main
的方法表示法将它们从do
中取出,对其进行一些简单的Maybe
计算(例如,可能将返回的所有Int
视为Nothing
),并将最终值返回为{ {1}}。
答案 0 :(得分:8)
那么签名本质上是错误的。结果应为Maybe Int
:
main :: Int -> Int -> Maybe Int
main x y = do
first <- compute x y
second <- compute (x+2) (y+2)
third <- compute (x+4) (y+4)
return (first + second + third)
例如,在这里我们return (first + second + third)
和return
会将它们包装在Just
数据构造函数中。
这是因为您的do
块隐式使用了>>=
中的Monad Maybe
,其定义为:
instance Monad Maybe where
Nothing >>=_ = Nothing
(Just x) >>= f = f x
return = Just
因此,这意味着它将确实从Just
数据构造函数中“解包”值,但是如果出现Nothing
,则意味着整个{{ 1}}块将为do
。
这或多或少为Nothing
提供了便利:您可以将计算作为一连串的成功动作进行,如果这些动作之一失败,结果将为{{ 1}},否则为Monad Maybe
。
因此,您最终不能返回Nothing
而不是Just result
,因为从类型的角度来看,一个或多个计算绝对可以实现 返回Int
。
但是,您可以“后”处理Maybe Int
块的结果,例如,如果添加一个“默认”值,该值将在计算之一为Nothing
的情况下使用,例如:
do
在这种情况下,如果Nothing
块返回一个import Data.Maybe(fromMaybe)
main :: Int -> Int -> Int
main x y = fromMaybe 0 $ do
first <- compute x y
second <- compute (x+2) (y+2)
third <- compute (x+4) (y+4)
return (first + second + third)
,我们将其替换为do
(当然,您可以在fromMaybe :: a -> Maybe a -> a
中添加另一个值作为值以防计算“失败”)。
如果要返回Nothing
列表中的第一个元素0
,则可以使用asum :: (Foldable t, Alternative f) => t (f a) -> f a
,这样就可以编写Maybe
喜欢:
Just
请注意,main
仍然只能包含-- first non-failing computation
import Data.Foldable(asum)
import Data.Maybe(fromMaybe)
main :: Int -> Int -> Int
main x y = fromMaybe 0 $ asum [
compute x y
compute (x+2) (y+2)
compute (x+4) (y+4)
]
,因此您仍然需要进行一些后处理。
答案 1 :(得分:3)
Willem的回答基本上是完美的,但只是为了真正说明问题,让我们考虑一下如果您编写允许返回整数的内容会发生什么情况。
因此,您拥有类型为ng serve
的{{1}}函数,让我们假设您的main
函数的实现如下:
Int -> Int -> Int
现在,这基本上是整数除法函数compute
的安全版本,如果除数为compute :: Int -> Int -> Maybe Int
compute a 0 = Nothing
compute a b = Just (a `div` b)
,它会返回div :: Int -> Int -> Int
。
如果您可以编写一个返回Nothing
的主函数,则可以编写以下代码:
0
这将使Int
失败并返回unsafe :: Int
unsafe = main 10 (-2)
,但是现在您必须将second <- compute ...
解释为不好的数字。它违反了使用Nothing
monad的全部目的,该安全可靠地捕获故障。当然,您可以按照Willem的描述为Nothing
设置默认值,但这并不总是合适的。
更一般而言,当您位于Maybe
块中时,您应该仅在monad的“盒子”内思考,不要试图逃脱。在某些情况下,例如Nothing
,您可能可以使用诸如do
或Maybe
之类的功能来进行unMaybe
,但一般而言并非如此。
答案 2 :(得分:1)
对于您的问题,我有两种解释,所以都可以回答它们:
Maybe Int
的{{1}}值求和以获得Just n
要在抛出Int
值的同时求和Maybe Int
,可以将Nothing
与Data.Maybe.catMaybes :: [Maybe a] -> [a]
结合使用,以从列表中抛出sum
值:>
Nothing
sum . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)]
的值Maybe Int
作为Just n
要获取第一个非Int
值,可以将Nothing
与listToMaybe :: [a] -> Maybe a
结合使用,以获取catMaybes
的第一个值(如果有一个或{{1} },如果没有,fromMaybe :: a -> Maybe a -> a
会将Just
转换为默认值:
Nothing
如果保证至少成功了一次,请改用Nothing
:
fromMaybe 0 . listToMaybe . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)]