【发布时间】:2018-03-27 09:15:41
【问题描述】:
我有一个函数在其参数上进行模式匹配以在StateT () Maybe () 中产生计算。此计算在运行时可能会失败,在这种情况下,我希望当前模式匹配分支失败。
我非常怀疑有可能有类似的东西
compute :: Int -> StateT () Maybe Int
compute = return
f :: Maybe Int -> Maybe Int -> StateT () Maybe ()
f (Just n1) (Just n2) = do
m <- compute (n1 + n2)
guard (m == 42)
f (Just n) _ = do
m <- compute n
guard (m == 42)
f _ (Just n) = do
m <- compute n
guard (m == 42)
按照我希望的方式行事:当第一次计算由于guard 或compute 的某个位置而失败时,我希望f 尝试下一个模式。
显然上述方法行不通,因为StateT(就像任何其他 monad 一样)在扩展时涉及一个附加参数,所以我可能无法将其表述为简单的模式保护。
以下是我想要的,但它很丑:
f' :: Maybe Int -> Maybe Int -> StateT () Maybe ()
f' a b = asum (map (\f -> f a b) [f1, f2, f3])
where
f1 a b = do
Just n1 <- pure a
Just n2 <- pure b
m <- compute (n1 + n2)
guard (m == 42)
f2 a _ = do
Just n <- pure a
m <- compute n
guard (m == 42)
f3 _ b = do
Just n <- pure b
m <- compute n
guard (m == 42)
像execStateT (f (Just 42) (Just 1)) () 这样的调用对于f 会失败,但对于f' 会返回Just (),因为它匹配f2。
如何获得f' 的行为,同时像f 那样使用尽可能少的辅助定义进行优雅的模式匹配?还有其他更优雅的方式来表达这个吗?
完整的可运行示例:
#! /usr/bin/env stack
-- stack --resolver=lts-11.1 script
import Control.Monad.Trans.State
import Control.Applicative
import Control.Monad
import Data.Foldable
compute :: Int -> StateT () Maybe Int
compute = return
f :: Maybe Int -> Maybe Int -> StateT () Maybe ()
f (Just n1) (Just n2) = do
m <- compute (n1 + n2)
guard (m == 42)
f (Just n) _ = do
m <- compute n
guard (m == 42)
f _ (Just n) = do
m <- compute n
guard (m == 42)
f' :: Maybe Int -> Maybe Int -> StateT () Maybe ()
f' a b = asum (map (\f -> f a b) [f1, f2, f3])
where
f1 a b = do
Just n1 <- pure a
Just n2 <- pure b
m <- compute (n1 + n2)
guard (m == 42)
f2 a _ = do
Just n <- pure a
m <- compute n
guard (m == 42)
f3 _ b = do
Just n <- pure b
m <- compute n
guard (m == 42)
main = do
print $ execStateT (f (Just 42) (Just 1)) () -- Nothing
print $ execStateT (f' (Just 42) (Just 1)) () -- Just (), because `f2` succeeded
编辑:到目前为止,我已经用这个问题得到了一些非常聪明的答案,谢谢!不幸的是,他们大多遭受我给出的特定代码示例的过度拟合。实际上,我需要这样的东西来统一两个表达式(确切地说是 let 绑定),如果可能的话,我想尝试统一两个同时 let 的 RHS 并解决我在一侧处理 let 绑定的情况漂浮它们的时间。所以,实际上Maybe 参数上没有巧妙的结构可以利用,而且我实际上不是computeing Int。
到目前为止的答案可能会使其他人受益,而不仅仅是他们给我带来的启发,所以谢谢!
编辑 2:以下是一些可能具有虚假语义的编译示例代码:
module Unify (unify) where
import Control.Applicative
import Control.Monad.Trans.State.Strict
data Expr
= Var String -- meta, free an bound vars
| Let String Expr Expr
-- ... more cases
-- no Eq instance, fwiw
-- | If the two terms unify, return the most general unifier, e.g.
-- a substitution (`Map`) of meta variables for terms as association
-- list.
unify :: [String] -> Expr -> Expr -> Maybe [(String, Expr)]
unify metaVars l r = execStateT (go [] [] l r) [] -- threads the current substitution as state
where
go locals floats (Var x) (Var y)
| x == y = return ()
go locals floats (Var x) (Var y)
| lookup x locals == Just y = return ()
go locals floats (Var x) e
| x `elem` metaVars = tryAddSubstitution locals floats x e
go locals floats e (Var y)
| y `elem` metaVars = tryAddSubstitution locals floats y e
-- case in point:
go locals floats (Let x lrhs lbody) (Let y rrhs rbody) = do
go locals floats lrhs rrhs -- try this one, fail current pattern branch if rhss don't unify
-- if we get past the last statement, commit to this branch, no matter
-- the next statement fails or not
go ((x,y):locals) floats lbody rbody
-- try to float the let binding. terms mentioning a floated var might still
-- unify with a meta var
go locals floats (Let x rhs body) e = do
go locals (Left (x,rhs):floats) body e
go locals floats e (Let y rhs body) = do
go locals (Right (y,rhs):floats) body e
go _ _ _ _ = empty
tryAddSubstitution = undefined -- magic
【问题讨论】:
-
“运行时计算可能会失败,在这种情况下,我希望整个模式匹配失败,可以这么说”似乎与“当第一次计算由于警卫或计算中的某处而失败时,我想让 f 尝试下一个模式”。如果你想有 2 种类型的失败 - 永久并尝试下一个模式,那么你应该有 2 层 Maybe
-
我认为从代码中可以很清楚地看出他的意思是当前分支下的计算应该失败。
-
没错。我编辑了那部分。
-
你对 a b 的分割模式是什么?也许它可以通过另一种方式来完成。类似于 generateCases a b >>= asum commute
标签: haskell pattern-matching monads