Alternative中的模式匹配:StateT () Maybe ()分支失败实现问询
实现分支计算失败时回溯到下一个模式匹配分支
嘿,我懂你的需求了——你想要的是:当某个模式匹配分支里的计算(比如guard不满足,或者compute本身返回失败)时,不是直接让整个f函数失败,而是自动回到模式匹配环节,尝试下一个分支。你的原始写法做不到这一点,因为Haskell的模式匹配是一旦匹配到分支就执行,执行失败则整个函数直接失败,不会回溯到模式匹配阶段。不过我们可以借助MonadPlus(也就是Alternative的monad版本)的特性来实现这个回溯效果。
核心思路
StateT () Maybe是MonadPlus的实例,这意味着它支持<|>操作符——这个操作符可以把两个计算组合起来:如果第一个计算失败,就自动尝试第二个计算。我们只需要把每个模式分支包装成一个“先检查模式是否匹配,匹配就执行计算,不匹配/计算失败就返回空(失败)”的计算,然后用<|>把它们串起来就行。
改写后的代码示例
compute :: Int -> StateT () Maybe Int compute = return -- 这里可以换成可能失败的实现 f :: Maybe Int -> Maybe Int -> StateT () Maybe () f m1 m2 = -- 第一个分支:两个参数都是Just的情况 (case (m1, m2) of (Just n1, Just n2) -> do m <- compute (n1 + n2) guard (m == 42) -- 这里的条件失败会触发回溯 _ -> empty) -- 不匹配这个模式就直接失败,尝试下一个分支 <|> -- 第二个分支:第一个参数是Just的情况 (case m1 of Just n -> do m <- compute n guard (m == 23) -- 替换成你需要的条件 _ -> empty)
为什么这样可行?
empty在StateT () Maybe中代表失败的计算(对应底层的Nothing)。当某个分支的模式不匹配时,我们返回empty,<|>就会自动尝试下一个分支。- 如果模式匹配成功,但分支里的计算(比如
guard不满足,或者compute返回失败),同样会产生empty,触发<|>去尝试下一个分支。 - 只有当某个分支的模式匹配成功,且所有计算都通过时,整个
f函数才会返回成功的计算。
更简洁的写法(用模式守卫)
如果你觉得case写法有点繁琐,也可以用模式守卫结合empty来实现:
import Data.Maybe (isJust) f m1 m2 = do -- 第一个分支 guard (isJust m1 && isJust m2) let Just n1 = m1 Just n2 = m2 m <- compute (n1 + n2) guard (m == 42) <|> -- 第二个分支 guard (isJust m1) let Just n = m1 m <- compute n guard (m == 23)
这种写法里,guard (isJust ...)用来检查模式是否匹配,不匹配的话就会失败,触发<|>尝试下一个分支。
举个实际例子
假设你调用f (Just 20) (Just 21):
- 第一个分支模式匹配成功,但
compute (20+21)返回41,guard (41 ==42)失败,第一个分支返回empty。 <|>自动尝试第二个分支,模式匹配成功,compute 20返回20,假设你的第二个分支guard是m==20,那这个分支就会成功,整个f返回成功的计算。
如果所有分支都失败(比如f (Just 10) (Just 10),两个分支的guard都不满足),那整个f就会返回empty,也就是底层的Nothing,符合预期。
内容的提问来源于stack exchange,提问作者Sebastian Graf
相关产品推荐
相关产品推荐

