【问题标题】:Exhaustive case with monadic guard带有单子保护的详尽案例
【发布时间】:2018-04-15 18:41:44
【问题描述】:

我有一个case 表达式,其模式数量相对较多:

case x of
  ... -> ...
  ... -> ...
  ... -> ...
  ... -> ...
  ...
  _ -> ...

其中一个案例有一个守卫:

case x of
  ... -> ...
  ... -> ...
  ... | condition -> ...
    -- If condition is false, fall through to “Rest”.

  -- Rest:
  ... -> ...
  ... -> ...
  ...
  _ -> ...

如果守卫不匹配,我们就直接处理剩下的情况,没问题。但现在我需要单子测试条件,所以我这样做了:

case x of
  ... -> ...
  ... -> ...
  ... -> do
    condition <- action
    if condition
      then ...
      else ...  -- How to fall through?

  -- Rest:
  ... -> ...
  ... -> ...
  ...
  _ -> ...

但是,我认为我犯了一个错误。似乎没有办法让else 分支继续处理剩余的案例,而不复制这些分支或将它们分解为一个函数。无论哪种方式都会导致穷举检查:如果我想在保护之后添加一个案例,编译器不知道匹配是否穷举。

如何更改此函数,或参数化/包装数据类型,以使用一元守卫进行详尽检查?

【问题讨论】:

  • 我想你自己搞糊涂了。如果Bar 匹配,您将只输入守卫(或if-then-else)。如果 Bar 匹配(并且您达到了条件),那么您已经知道 BazQuux 等不匹配,因此没有理由继续使用 case 块。
  • @BenjaminHodgson:很好。更新了示例。我正在对一对值进行操作,以后的情况仍有可能匹配。
  • 不,不是。您能否只提供您正在使用的实际案例,而不是一个编造的名称,但实际上并没有显示您在做什么?
  • @Bakuriu:真实的代码比较复杂,我不知道它是否真的相关,但我可以稍后更新示例。实际上,我有一个构造函数:@,而我的Bar 案例实际上是Ctor "Join" :@ a :@ b 上的匹配项,它在匹配a :@ b 的更通用案例之前出现。
  • @JonPurdy 您不必发布您的真实代码,但您需要发布一个实际可执行示例来演示问题。

标签: haskell pattern-matching monads


【解决方案1】:

我不喜欢下面的方法,但无论如何我都会分享它:

fix (\proceed b -> case (x, y, b) of
  (Foo ..., Foo ..., False) -> ...
  (Bar ..., Bar ..., False) -> do
    condition <- action
    if condition
      then ...
      else proceed True
  (Baz ..., ..., _) -> ...
  (Var ..., ..., _) -> ...
  ...
) False

附加标志b 最初为假,因此考虑了所有分支。一旦我们proceed,我们将其设置为 true 以便跳过第一个分支。

可能会或可能不会静态地发现详尽无遗,具体取决于实际模式。

【讨论】:

    【解决方案2】:

    一个简单的选择是获取case 块的后半部分并将其放入单独的函数中。

    case (x, y) of
      (Foo ..., Foo ...) -> ...
      x@(Bar ..., Bar ...) -> do
        condition <- action
        if condition
        then ...
        else rest x
      x -> rest x
    
    rest (Baz ..., ...) = ...
    rest (Var ..., ...) = ...
    ...
    rest _ = undefined
    

    rest 的贯穿案例中使用undefined 来捕获您认为应该在原始case 块的前半部分匹配的模式有点令人不满意。如果您设法违反前提条件((Foo, Foo) 等不匹配),那么您将收到运行时错误。

    【讨论】:

    • 如果失败中的匹配是相对具体的,例如x :@ y(正如@JonPurdy 似乎在评论中暗示的那样),您可以将rest 设为两个参数函数以避免使用一个偏函数。
    【解决方案3】:

    我最终做的是使用配备 Maybe 的 monad 堆栈,并按照以下结构将 case 替换为 asum 在操作列表上:

    asum
    
      [ do
        pattern1 <- pure input  -- failed pattern matches fall through
        body1
    
      , do
        pattern2 <- pure input
        body2
    
      , do
        pattern1 <- pure input
        guard condition         -- guards also fall through
        body3
    
      , ...
    
      ]
    

    正如我最近在this answer 中对别人的问题所描述的那样。不幸的是,这不允许详尽检查。

    【讨论】:

      【解决方案4】:

      显而易见的问题是:你能不能不解开纯净和不纯净的部分? 没有实际代码很难判断,但如果实际上只有一个条件是问题,您可以使用 Either 或 Maybe 进行两级案例,这会捕获所有特定条件。

      step <-case x of 
         pattern2 -> condition <- action
                      if condition 
                      then Just whatToDo
                      else Nothing
      
         pattern5 -> condition <- action2
                      if condition
                      then Just whatToDo2
      
         _ -> Nothing
      
      case step of
        Just action -> action
        Nothing -> case x of
             pattern 1 -> body1
             ....
      

      根据您的实际情况,您可能想要使用不同的中间类型,甚至可能创建自己的自定义类型,或者意识到您实际上甚至不需要任何中间类型。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-14
        • 2022-08-05
        • 2017-04-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多