重用模式守卫或案例表达式中的模式 [英] Reusing patterns in pattern guards or case expressions
问题描述
数据表达式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)
。
我不确定这是否适用于您的示例,但我希望它可以为您提供一个好的起点。你可能想要通过整个事件来处理额外的错误处理,但好消息是大部分都被折叠到 我提出一个类似的解决方案一个相关的(但有点不同)的问题。它也是一种处理原生Haskell值和解释器之间来回切换的方式,但解释器的结构是不同的。一些相同的想法仍然适用于! My Haskell project includes an expression evaluator, which for the purposes of this question can be simplified to: The straightforward approach to implementing this is to write a To me, that definition looks somewhat awkward, so I then rewrote the definition using pattern guards, like so: I think this definition looks cleaner compared to the 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 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 Then each specific case looks like this: Which isn't too bad: it isn't overly redundant. It encompasses the information that matters: 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 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 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屋! pack 的定义中,
unpack
和 liftOp
,所以它仍然非常集中。
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 ...
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.
reduce (Add x y) | I x' <- reduce x
, I y' <- reduce y
= I $ x' + y'
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'
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.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'
Mul x y -> liftOp Mul (*) x y
Mul
gets mapped to *
, and in the error case we just apply Mul
again.(Extract a, Pack b) => Pack (a -> b)
.pack
, unpack
and liftOp
, so it's still pretty centralized.