重用模式守卫或案例表达式中的模式 [英] Reusing patterns in pattern guards or case expressions

查看:106
本文介绍了重用模式守卫或案例表达式中的模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的Haskell项目包含一个表达式求值器,它可以简化为:

 数据表达式a其中
I :: Int - >表达式Int
B :: Bool - >表达式Bool
Add :: Expression Int - >表达式Int - >表达式Int
Mul ::表达式Int - >表达式Int - >表达式Int
Eq ::表达式Int - >表达式Int - >表达式Bool
And :: Expression Bool - >表达式Bool - >表达式Bool
Or :: Expression Bool - >表达式Bool - >表达式Bool
如果:: Expression Bool - >表达式a - >表达式a - >表达式a

- 将表达式简化为最简单的表示形式。
reduce ::表达式a - >表达式a
- ...实现...

直接实现此方法是写一个 case 表达式递归地评估和模式匹配,就像这样:

  reduce(Add xy)= case(reduce x,reduce y)
(I x',I y') - > I $ x'+ y'
(x',y') - >添加x'y'
reduce(Mul x y)= case(reduce x,reduce y)
(I x',I y') - > I $ x'* y'
(x',y') - >
(B x',B y') - >的情况(x,y)
reduce(和x y)= case B $ x'&& y'
(x',y') - >而x'y'
- ...和其他情况类似。

对我而言,这个定义看起来有些尴尬,于是我使用模式守卫重写了这个定义,就像这样:

  reduce(Add xy)| I x'<  - 减少x 
,I y'< - 减少y
= I $ x'+ y'

我认为这个定义与 case 表达式相比看起来更清晰,但是当为不同的构造函数定义多个模式时,模式会重复

  reduce(Add xy)| I x'<  - 减少x 
,I y'< - 减少y
= I $ x'+ y'
reduce(Mul x y)| I x'< - 减少x
,I y'< - 减少y
= I $ x'* y'

注意到这些重复的模式,我希望会有一些语法或结构可以减少模式匹配中的重复。是否有普遍接受的方法来简化这些定义?



编辑:在审查模式守卫后,我意识到他们不起作用作为这里的替代品。尽管当 x y 可以被减少到 I _ ,它们在模式防护不匹配时不会减少任何值。我仍然会喜欢 reduce 来简化 Add 等的子表达式。


这个想法是创建两个用于去往或来自自定义类型的类型类型,并具有适当的错误处理。然后,您可以使用它们来创建一个 liftOp 函数,如下所示:

  liftOp ::(提取a,提取b,包c)=> (a→b→c)→> 
(表达式a - >表达式b - >表达式c)
liftOp err op a b =案例res
Nothing - > err a'b'
只需res - > pack res
where res = do a'< - extract $ reduce'a
b'< - extract $ reduce'b
return $ a'`op` b'

然后,每个特定的案例如下所示:

  Mul xy  - > liftOp Mul(*)x y 

这并不是太糟糕:它不是过度冗余。它包含重要的信息: Mul 被映射为 * ,并且在错误情况下,我们只应用 Mul



您还需要打包和解包的实例,但这些实例无论如何都是有用的。一个巧妙的诀窍是,它们还可以让你使用(Extract a,Pack b)=>形式的实例自动地将函数嵌入到你的DSL中。 Pack(a - > b)



我不确定这是否适用于您的示例,但我希望它可以为您提供一个好的起点。你可能想要通过整个事件来处理额外的错误处理,但好消息是大部分都被折叠到 pack 的定义中, unpack liftOp ,所以它仍然非常集中。

提出一个类似的解决方案一个相关的(但有点不同)的问题。它也是一种处理原生Haskell值和解释器之间来回切换的方式,但解释器的结构是不同的。一些相同的想法仍然适用于!


My Haskell project includes an expression evaluator, which for the purposes of this question can be simplified to:

data Expression a where
    I :: Int -> Expression Int
    B :: Bool -> Expression Bool
    Add :: Expression Int  -> Expression Int  -> Expression Int
    Mul :: Expression Int  -> Expression Int  -> Expression Int
    Eq  :: Expression Int  -> Expression Int  -> Expression Bool
    And :: Expression Bool -> Expression Bool -> Expression Bool
    Or  :: Expression Bool -> Expression Bool -> Expression Bool
    If  :: Expression Bool -> Expression a    -> Expression a -> Expression a

-- Reduces an Expression down to the simplest representation.
reduce :: Expression a -> Expression a
-- ... implementation ...

The straightforward approach to implementing this is to write a case expression to recursively evaluate and pattern match, like so:

reduce (Add x y) = case (reduce x, reduce y) of
                    (I x', I y') -> I $ x' + y'
                    (x', y')     -> Add x' y'
reduce (Mul x y) = case (reduce x, reduce y) of
                    (I x', I y') -> I $ x' * y'
                    (x', y')     -> Mul x' y'
reduce (And x y) = case (reduce x, reduce y) of
                    (B x', B y') -> B $ x' && y'
                    (x', y')     -> And x' y'
-- ... and similarly for other cases.

To me, that definition looks somewhat awkward, so I then rewrote the definition using pattern guards, like so:

reduce (Add x y) | I x' <- reduce x
                 , I y' <- reduce y
                 = I $ x' + y'

I think this definition looks cleaner compared to the case expression, but when defining multiple patterns for different constructors, the pattern is repeated multiple times.

reduce (Add x y) | I x' <- reduce x
                 , I y' <- reduce y
                 = I $ x' + y'
reduce (Mul x y) | I x' <- reduce x
                 , I y' <- reduce y
                 = I $ x' * y'

Noting these repeated patterns, I was hoping there would be some syntax or structure that could cut down on the repetition in the pattern matching. Is there a generally accepted method to simplify these definitions?

Edit: after reviewing the pattern guards, I've realised they don't work as a drop-in replacement here. Although they provide the same result when x and y can be reduced to I _, they do not reduce any values when the pattern guards do not match. I would still like reduce to simplify subexpressions of Add et al.

解决方案

One partial solution, which I've used in a similar situation, is to extract the logic into a "lifting" function that takes a normal Haskell operation and applies it to your language's values. This abstracts over the wrappping/unwrapping and resulting error handling.

The idea is to create two typeclasses for going to and from your custom type, with appropriate error handling. Then you can use these to create a liftOp function that could look like this:

liftOp :: (Extract a, Extract b, Pack c) => (a -> b -> c) -> 
            (Expression a -> Expression b -> Expression c)
liftOp err op a b = case res of
  Nothing  -> err a' b'
  Just res -> pack res
  where res = do a' <- extract $ reduce' a
                 b' <- extract $ reduce' b
                 return $ a' `op` b'

Then each specific case looks like this:

Mul x y -> liftOp Mul (*) x y

Which isn't too bad: it isn't overly redundant. It encompasses the information that matters: Mul gets mapped to *, and in the error case we just apply Mul again.

You would also need instances for packing and unpacking, but these are useful anyhow. One neat trick is that these can also let you embed functions in your DSL automatically, with an instance of the form (Extract a, Pack b) => Pack (a -> b).

I'm not sure this will work exactly for your example, but I hope it gives you a good starting point. You might want to wire additional error handling through the whole thing, but the good news is that most of that gets folded into the definition of pack, unpack and liftOp, so it's still pretty centralized.

I wrote up a similar solution for a related (but somewhat different) problem. It's also a way to handle going back and forth between native Haskell values and an interpreter, but the interpreter is structured differently. Some of the same ideas should still apply though!

这篇关于重用模式守卫或案例表达式中的模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆